Install
$ agentstack add skill-ab604-claude-code-r-skills-tdd-workflow ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Test-Driven Development Workflow for R
This skill ensures all R code development follows TDD principles with comprehensive test coverage using testthat.
When to Activate
- Writing new functions or features
- Fixing bugs or issues
- Refactoring existing code
- Adding new model types
- Creating data processing pipelines
- Building Shiny components
Getting Started
Initialize testing infrastructure for your package:
# Set up testthat (Edition 3)
usethis::use_testthat(3)
# Create a test file for an existing source file
usethis::use_test("function_name")
# Or create test and source file together
usethis::use_r("function_name")
usethis::use_test("function_name")
Core Principles
1. Tests BEFORE Code
ALWAYS write tests first, then implement code to make tests pass.
2. Coverage Requirements
- Minimum 80% coverage (unit + integration)
- 100% coverage for statistical calculations
- 100% coverage for data validation
- All edge cases covered
- Error scenarios tested
3. Test Types
Tests follow a three-level hierarchy: File → Test → Expectation
Unit Tests
Individual functions and utilities:
test_that("rescale01 normalizes to [0, 1] range", {
expect_equal(rescale01(c(0, 5, 10)), c(0, 0.5, 1))
expect_equal(rescale01(c(-10, 0, 10)), c(0, 0.5, 1))
})
test_that("rescale01 handles edge cases", {
expect_equal(rescale01(c(5, 5, 5)), c(NaN, NaN, NaN))
expect_equal(rescale01(numeric(0)), numeric(0))
expect_equal(rescale01(c(0, NA, 10)), c(0, NA, 1))
})
Integration Tests
Function interactions and workflows:
test_that("data pipeline produces expected output", {
raw_data
clean_data() |>
transform_features() |>
summarize_results()
expect_s3_class(result, "tbl_df")
expect_named(result, c("group", "mean", "sd", "n"))
expect_true(all(result$n > 0))
})
Snapshot Tests
For complex outputs that are hard to specify:
test_that("model summary format is stable", {
model ci_95["upper"] - ci_95["lower"])
})
test_that("calculate_ci handles NA values", {
set.seed(123)
result = 1) {
cli::cli_abort("{.arg conf_level} must be between 0 and 1", class = "validation_error")
}
# Remove NA values
x = 1) {
cli::cli_abort("{.arg conf_level} must be between 0 and 1", class = "validation_error")
}
}
calculate_ci <- function(x, conf_level = 0.95, n_boot = 1000) {
validate_ci_inputs(x, conf_level)
x <- x[!is.na(x)]
boot_means <- replicate(n_boot, mean(sample(x, replace = TRUE)))
alpha <- 1 - conf_level
c(
lower = unname(quantile(boot_means, alpha / 2)),
upper = unname(quantile(boot_means, 1 - alpha / 2))
)
}
Step 7: Verify Coverage
covr::package_coverage()
# calculate_ci.R: 100%
Testing Patterns
Testing Data Transformations
test_that("clean_data removes invalid rows", {
input <- tibble(
id = 1:4,
value = c(1, NA, 3, -999)
)
result <- clean_data(input, invalid_value = -999)
expect_equal(nrow(result), 2)
expect_equal(result$id, c(1, 3))
expect_false(anyNA(result$value))
})
Testing Statistical Functions
test_that("weighted_mean matches manual calculation", {
x <- c(1, 2, 3)
w <- c(1, 2, 1)
result <- weighted_mean(x, w)
expected <- sum(x * w) / sum(w) # (1 + 4 + 3) / 4 = 2
expect_equal(result, expected)
})
Testing with Fixtures
# helper-fixtures.R
read_fixture <- function(name) {
path <- testthat::test_path("fixtures", name)
readr::read_csv(path, show_col_types = FALSE)
}
# test-pipeline.R
test_that("pipeline handles real data", {
input <- read_fixture("sample_data.csv")
result <- process_pipeline(input)
expect_snapshot(result)
})
Mocking External Dependencies
test_that("fetch_data handles API errors", {
# Mock the API call
local_mocked_bindings(
httr2_request = function(...) {
stop("API unavailable")
}
)
expect_error(
fetch_data("endpoint"),
"API unavailable"
)
})
Using withr for Cleanup
Use withr functions to manage temporary state with automatic restoration:
test_that("function respects options", {
# Temporarily set options
withr::local_options(list(digits = 2))
result <- format_number(3.14159)
expect_equal(result, "3.14")
})
test_that("function writes to temp file", {
# Create temp file that's automatically cleaned up
tmp <- withr::local_tempfile(lines = c("line 1", "line 2"))
result <- process_file(tmp)
expect_equal(result$n_lines, 2)
})
test_that("function uses custom environment variable", {
# Temporarily set env var
withr::local_envvar(MY_VAR = "test_value")
result <- get_config()
expect_equal(result$my_var, "test_value")
})
Test Data Strategies
Choose the appropriate approach for your testing needs:
1. Constructor Functions
Create data on-demand with helper functions:
# helper-data.R
make_sample_data <- function(n = 100) {
tibble(
id = 1:n,
group = sample(c("A", "B"), n, replace = TRUE),
value = rnorm(n)
)
}
# test-analysis.R
test_that("analysis handles grouped data", {
data <- make_sample_data(n = 50)
result <- analyze_groups(data)
expect_s3_class(result, "tbl_df")
})
2. Local Functions with Cleanup
Handle side effects using withr:
test_that("function reads CSV correctly", {
# Create temp file with cleanup
tmp <- withr::local_tempfile(fileext = ".csv")
write.csv(mtcars, tmp, row.names = FALSE)
result <- read_and_process(tmp)
expect_equal(nrow(result), 32)
})
3. Static Fixtures
Store data files in fixtures/ directory:
# Store in: tests/testthat/fixtures/sample_data.csv
test_that("function handles real data format", {
path <- test_path("fixtures", "sample_data.csv")
data <- read_csv(path)
result <- process_data(data)
expect_true(all(result$valid))
})
Common Testing Mistakes to Avoid
WRONG: Testing Implementation Details
# Don't test internal state
expect_equal(obj$internal_cache, expected_cache)
CORRECT: Test Behavior
# Test observable behavior
expect_equal(get_result(obj), expected_result)
WRONG: Brittle Tests
# Breaks on any output change
expect_equal(as.character(result), "Mean: 5.234567890")
CORRECT: Flexible Assertions
# Robust to formatting changes
expect_equal(result$mean, 5.23, tolerance = 0.01)
WRONG: Dependent Tests
test_that("creates data", { global_data <<- create() })
test_that("uses data", { process(global_data) }) # Depends on previous!
CORRECT: Independent Tests
test_that("creates and uses data", {
data <- create()
result <- process(data)
expect_true(is_valid(result))
})
WRONG: Modifying Tests to Pass
# When a test fails, don't change the test (unless it's wrong)
test_that("function returns 42", {
expect_equal(my_function(), 42) # Test fails
})
# DON'T DO THIS:
test_that("function returns 41", {
expect_equal(my_function(), 41) # Changed to pass - WRONG!
})
CORRECT: Fix the Implementation
# Fix the code to match expected behavior
test_that("function returns 42", {
expect_equal(my_function(), 42) # Test fails
})
# Fix my_function() implementation instead
When Tests Fail
- Do NOT modify tests to make them pass (unless the test is wrong)
- Fix the implementation to match expected behavior
- Add more tests if the failure reveals missing coverage
- Update snapshots only if the change is intentional
# Review and accept snapshot changes
testthat::snapshot_review("test_name")
testthat::snapshot_accept("test_name")
Coverage Verification
# Run coverage report
covr::package_coverage()
# Interactive HTML report
covr::report()
# Check specific thresholds
cov <- covr::package_coverage()
pct <- covr::percent_coverage(cov)
if (pct < 80) {
stop("Coverage below 80%: ", round(pct, 1), "%")
}
# In testthat.R or as a coverage check
covr::package_coverage(
type = "all",
line_coverage = 0.80,
function_coverage = 0.80
)
Debugging & Development
Running Tests at Different Scales
# Micro: Interactive development
devtools::load_all()
expect_equal(my_function(1), 1) # Direct expectation
# Mezzo: Single file
testthat::test_file("tests/testthat/test-validation.R")
# RStudio: Ctrl/Cmd+Shift+T
# Macro: Full suite
devtools::test()
devtools::check() # Full package validation
Test Reporters
# Find slow tests
devtools::test(reporter = "slow")
# Progress reporter (verbose)
devtools::test(reporter = "progress")
# Test execution order independence
devtools::test(shuffle = TRUE)
Continuous Testing
# Watch mode - auto-run on file changes
testthat::auto_test_package()
Parallel Execution (Edition 3)
Edition 3 supports parallel test execution for faster runs on multi-core systems.
Running Tests
# All tests
devtools::test()
# All tests (keyboard shortcut)
# RStudio: Ctrl/Cmd+Shift+T
# With coverage
covr::package_coverage()
# Specific file
testthat::test_file("tests/testthat/test-validation.R")
# Watch mode
testthat::auto_test_package()
# Verbose output
devtools::test(reporter = "progress")
# Find slow tests
devtools::test(reporter = "slow")
# Test independence
devtools::test(shuffle = TRUE)
# Full package check
devtools::check()
Success Metrics
- 80%+ code coverage achieved
- All tests passing
- No skipped tests
- Fast execution (< 30s for unit tests)
- Tests catch bugs before production
- Confident refactoring enabled
- Tests run independently in any order
- Clear, descriptive test names
- Each test validates one concept
Remember: Tests are not optional. They are the safety net that enables confident refactoring, rapid development, and production reliability. Write them FIRST.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: ab604
- Source: ab604/claude-code-r-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.