| name | tdd-workflow |
| description | Test-driven development workflow for qcthat. Use when writing any R code (writing new features, fixing bugs, refactoring, or reviewing tests). |
TDD workflow for qcthat
Core principle
Write a failing test first, then implement the minimal code to make it pass,
then refactor. Never write implementation code without a failing test driving it.
File naming
Tests for R/{FunctionName}.R go in tests/testthat/test-{FunctionName}.R.
Place new tests next to similar existing ones.
Running tests
devtools::test(reporter = "check")
devtools::test(filter = "FunctionName", reporter = "check")
Testing functions load code automatically. You do not need to call library() or devtools::load_all() separately.
Coverage
Goal: 100% for every edited file. After editing R/FunctionName.R, verify:
covr_res <- devtools:::test_coverage_active_file("R/FunctionName.R")
which(purrr::map_int(covr_res, "value") == 0)
Files excluded from the coverage requirement:
R/qcthat-package.R
R/aaa-shared.R
Test types
Unit tests
Test individual functions in isolation:
test_that("FetchRepoIssues() returns a qcthat_Issues tibble (#2)", {
result <- FetchRepoIssues()
expect_s3_class(result, "qcthat_Issues")
})
Snapshot tests
For complex outputs that are hard to specify with equality assertions:
test_that("print method is stable (#123)", {
expect_snapshot(print(CompileTestResults(test_results_object)))
})
test_that("errors on invalid input (#456)", {
expect_snapshot({
(expect_error(
FetchRepoIssues(strOwner = 123),
class = "qcthat-error"
))
})
})
When snapshots change intentionally:
testthat::snapshot_review("test_name")
testthat::snapshot_accept("test_name")
Snapshots are stored in tests/testthat/_snaps/.
Test design principles
testthat Edition 3 — deprecated patterns
context("Data validation")
expect_equivalent(x, y)
with_mock(...)
expect_is(x, "data.frame")
Essential expectations
Equality & identity
expect_equal(x, y)
expect_equal(x, y, tolerance = 0.001)
expect_equal(x, y, ignore_attr = TRUE)
expect_identical(x, y)
Conditions
expect_error(code)
expect_error(code, "pattern")
expect_error(code, class = "qcthat-error")
expect_error(code, class = "qcthat-error-bad_input")
expect_warning(code)
expect_no_warning(code)
expect_message(code)
expect_no_message(code)
Collections
expect_setequal(x, y)
expect_in(element, set)
expect_named(x, c("a", "b"))
Type & structure
expect_type(x, "double")
expect_s3_class(x, "qcthat_Issues")
expect_length(x, 10)
Logical
expect_true(x)
expect_false(x)
expect_null(x)
withr patterns for temporary state
withr::local_options(list(qcthat.option = TRUE))
withr::local_envvar(MY_VAR = "value")
withr::local_tempfile(lines = c("a", "b"))
Fixtures
Store static test data in tests/testthat/fixtures/ and access via:
test_path("fixtures", "sample.rds")
Mocking
local_mocked_bindings(
ExternalFn = function(...) "mocked_result"
)
result <- MyFunctionThatCallsExternalFn()
Common mistakes
- Do not modify tests to make them pass. Fix the implementation.
- Do not write tests that depend on other tests' state. Each test must
be independently runnable.