Skip to content
Development
Skill

/tdd-workflow

Test-driven development workflow for R using testthat. Use when writing new features, fixing bugs, or refactoring code. Enforces test-first development with 80%+ coverage.

From plugin
r-skills
1898 skills3 agents4 commands4 hooks
Install
$ npx -y skills add ab604/claude-code-r-skills --skill tdd-workflow --agent claude-code

How it fires

How this skill gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.
  • Slash command/tdd-workflow

Context preview

The summary Claude sees to decide when to auto-load this skill.

Test-driven development workflow for R using testthat. Use when writing new features, fixing bugs, or refactoring code. Enforces test-first development with 80%+ coverage.

SKILL.md

tdd-workflow.SKILL.md
name: tdd-workflow
description: Test-driven development workflow for R using testthat. Use when writing new features, fixing bugs, or refactoring code. Enforces test-first development with 80%+ coverage.

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 <- read_fixture("sample_input.csv")

  result <- 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 <- fit_model(test_data)
  expect_snapshot(print(summary(model)))
})

test_that("error messages are informative", {
  expect_snapshot(
    validate_input(invalid_data),
    error = TRUE
  )
})

**Snapshot workflow:**

# Review snapshot changes
testthat::snapshot_review("test_name")

# Accept snapshot changes
testthat::snapshot_accept("test_name")

Snapshots are stored in `tests/testthat/_snaps/` directory.

BDD Alternative (Optional)

For behavior-driven development, use `describe()` and `it()`:

describe("matrix()", {
  it("can be multiplied by a scalar", {
    m1 <- matrix(1:4, 2, 2)
    m2 <- m1 * 2
    expect_equal(matrix(c(2, 4, 6, 8), 2, 2), m2)
  })

  it("can be transposed", {
    m <- matrix(1:4, 2, 2)
    expect_equal(t(m), matrix(c(1, 3, 2, 4), 2, 2))
  })
})

**Key distinction:** "describe() verifies you implement the right things, test_that() ensures you do things right."

Test Design Principles

Self-Sufficient Tests

Each test should contain all setup, execution, and teardown code. Tests must be independent and runnable in isolation without relying on ambient state or prior test execution.

# GOOD: Self-contained
test_that("function works with specific data", {
  data <- tibble(x = 1:10, y = rnorm(10))  # Setup
  result <- my_function(data)               # Execute
  expect_equal(nrow(result), 10)            # Assert
})

# BAD: Depends on external state
# setup_data <- tibble(...)  # Created outside test
test_that("function works", {
  result <- my_function(setup_data)  # Relies on external data
  expect_equal(nrow(result), 10)
})

Duplication Over Factoring

Repetition is acceptable in tests—duplicate setup code rather than extracting it elsewhere. Clarity outweighs avoiding duplication.

# GOOD: Duplicated but clear
test_that("clean_data handles missing values", {
  data <- tibble(x = c(1, NA, 3), y = c(4, 5, 6))
  result <- clean_data(data)
  expect_equal(nrow(result), 2)
})

test_that("clean_data handles invalid values", {
  data <- tibble(x = c(1, -999, 3), y = c(4, 5, 6))
  result <- clean_data(data, invalid = -999)
  expect_equal(nrow(result), 2)
})

# ACCEPTABLE: Each test is self-contained and readable

Plan for Failure

Write tests assuming they'll fail and require debugging. Make logic explicit and obvious. Run tests in fresh R sessions independently.

Use devtools::load_all()

During development, prefer `devtools::load_all()` over `library()`. This:

  • Exposes unexported functions for testing
  • Automatically attaches testthat
  • Eliminates unnecessary `library()` calls in tests
  • Simulates package loading without installation

testthat Edition 3

Edition 3 provides improved snapshot testing, better diffs via waldo, unified condition handling, parallel execution support, and byte-compiled code compatibility for mocking.

Deprecated Patterns → Modern Alternatives

# DEPRECATED: context() calls
context("Data validation")  # Remove - filename serves this purpose

# DEPRECATED: expect_equivalent()
expect_equivalent(x, y)
# MODERN:
expect_equal(x, y, ignore_attr = TRUE)

# DEPRECATED: with_mock()
with_mock(external_call = function() "mocked", {
  result <- my_function()
})
# MODERN:
local_mocked_bindings(
  external_call = function() "mocked"
)
result <- my_function()

# DEPRECATED: expect_is()
expect_is(x, "data.frame")
# MODERN:
expect_s3_class(x, "data.frame")

Initialize Edition 3

In `DESCRIPTION`, ensure:

Config/testthat/edition: 3

Or initialize with:

usethis::use_testthat(3)

Essential Expectations Reference

Equality & Identity

expect_equal(x, y)              # With numeric tolerance
expect_equal(x, y, tolerance = 0.001)
expect
Read more
Ships withr-skills

A curated collection of Claude Code configurations for modern R use. These skills, rules, commands, and agents help Claude Code understand R best practices and generate idiomatic, high-quality R code.

Get the whole plugin