testing-r-packages

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing R Packages with testthat

使用testthat测试R包

Modern best practices for R package testing using testthat 3+.
使用testthat 3+进行R包测试的现代最佳实践。

Initial Setup

初始设置

Initialize testing with testthat 3rd edition:
r
usethis::use_testthat(3)
This creates
tests/testthat/
directory, adds testthat to
DESCRIPTION
Suggests with
Config/testthat/edition: 3
, and creates
tests/testthat.R
.
使用testthat第3版初始化测试:
r
usethis::use_testthat(3)
这会创建
tests/testthat/
目录,将testthat添加到
DESCRIPTION
的Suggests字段并设置
Config/testthat/edition: 3
,同时创建
tests/testthat.R
文件。

File Organization

文件组织

Mirror package structure:
  • Code in
    R/foofy.R
    → tests in
    tests/testthat/test-foofy.R
  • Use
    usethis::use_r("foofy")
    and
    usethis::use_test("foofy")
    to create paired files
Special files:
  • helper-*.R
    - Helper functions and custom expectations, sourced before tests
  • setup-*.R
    - Run during
    R CMD check
    only, not during
    load_all()
  • fixtures/
    - Static test data files accessed via
    test_path()
镜像包结构:
  • 代码位于
    R/foofy.R
    → 测试位于
    tests/testthat/test-foofy.R
  • 使用
    usethis::use_r("foofy")
    usethis::use_test("foofy")
    创建配对文件
特殊文件:
  • helper-*.R
    - 辅助函数和自定义断言,在测试前加载
  • setup-*.R
    - 仅在
    R CMD check
    期间运行,
    load_all()
    时不运行
  • fixtures/
    - 通过
    test_path()
    访问的静态测试数据文件

Test Structure

测试结构

Tests follow a three-level hierarchy: File → Test → Expectation
测试遵循三级层级:文件 → 测试 → 断言

Standard Syntax

标准语法

r
test_that("descriptive behavior", {
  result <- my_function(input)
  expect_equal(result, expected_value)
})
Test descriptions should read naturally and describe behavior, not implementation.
r
test_that("描述性行为", {
  result <- my_function(input)
  expect_equal(result, expected_value)
})
测试描述应自然表述,描述行为而非实现细节。

BDD Syntax (describe/it)

BDD语法(describe/it)

For behavior-driven development, use
describe()
and
it()
:
r
describe("matrix()", {
  it("can be multiplied by a scalar", {
    m1 <- matrix(1:4, 2, 2)
    m2 <- m1 * 2
    expect_equal(matrix(1:4 * 2, 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 features:
  • describe()
    groups related specifications for a component
  • it()
    defines individual specifications (like
    test_that()
    )
  • Supports nesting for hierarchical organization
  • it()
    without code creates pending test placeholders
Use
describe()
to verify you implement the right things, use
test_that()
to ensure you do things right.
See references/bdd.md for comprehensive BDD patterns, nested specifications, and test-first workflows.
对于行为驱动开发,使用
describe()
it()
r
describe("matrix()", {
  it("可以与标量相乘", {
    m1 <- matrix(1:4, 2, 2)
    m2 <- m1 * 2
    expect_equal(matrix(1:4 * 2, 2, 2), m2)
  })

  it("可以转置", {
    m <- matrix(1:4, 2, 2)
    expect_equal(t(m), matrix(c(1, 3, 2, 4), 2, 2))
  })
})
核心特性:
  • describe()
    对组件的相关规格进行分组
  • it()
    定义单个规格(类似
    test_that()
  • 支持嵌套以实现层级化组织
  • 无代码的
    it()
    会创建待处理的测试占位符
使用
describe()
验证你是否实现了正确的功能,使用
test_that()
确保你正确实现了功能。
查看references/bdd.md获取全面的BDD模式、嵌套规格和测试优先工作流。

Running Tests

运行测试

Three scales of testing:
Micro (interactive development):
r
devtools::load_all()
expect_equal(foofy(...), expected)
Mezzo (single file):
r
testthat::test_file("tests/testthat/test-foofy.R")
三种测试规模:
微测试(交互式开发):
r
devtools::load_all()
expect_equal(foofy(...), expected)
中测试(单个文件):
r
testthat::test_file("tests/testthat/test-foofy.R")

RStudio: Ctrl/Cmd + Shift + T

RStudio: Ctrl/Cmd + Shift + T


**Macro** (full suite):
```r
devtools::test()    # Ctrl/Cmd + Shift + T
devtools::check()   # Ctrl/Cmd + Shift + E

**宏测试**(完整套件):
```r
devtools::test()    # Ctrl/Cmd + Shift + T
devtools::check()   # Ctrl/Cmd + Shift + E

Core Expectations

核心断言

Equality

相等性

r
expect_equal(10, 10 + 1e-7)      # Allows numeric tolerance
expect_identical(10L, 10L)       # Exact match required
expect_all_equal(x, expected)    # Every element matches (v3.3.0+)
r
expect_equal(10, 10 + 1e-7)      # 允许数值容差
expect_identical(10L, 10L)       # 需要完全匹配
expect_all_equal(x, expected)    # 每个元素都匹配(v3.3.0+)

Errors, Warnings, Messages

错误、警告、消息

r
expect_error(1 / "a")
expect_error(bad_call(), class = "specific_error_class")
expect_no_error(valid_call())

expect_warning(deprecated_func())
expect_no_warning(safe_func())

expect_message(informative_func())
expect_no_message(quiet_func())
r
expect_error(1 / "a")
expect_error(bad_call(), class = "specific_error_class")
expect_no_error(valid_call())

expect_warning(deprecated_func())
expect_no_warning(safe_func())

expect_message(informative_func())
expect_no_message(quiet_func())

Pattern Matching

模式匹配

r
expect_match("Testing is fun!", "Testing")
expect_match(text, "pattern", ignore.case = TRUE)
r
expect_match("Testing is fun!", "Testing")
expect_match(text, "pattern", ignore.case = TRUE)

Structure and Type

结构与类型

r
expect_length(vector, 10)
expect_type(obj, "list")
expect_s3_class(model, "lm")
expect_s4_class(obj, "MyS4Class")
expect_r6_class(obj, "MyR6Class")      # v3.3.0+
expect_shape(matrix, c(10, 5))         # v3.3.0+
r
expect_length(vector, 10)
expect_type(obj, "list")
expect_s3_class(model, "lm")
expect_s4_class(obj, "MyS4Class")
expect_r6_class(obj, "MyR6Class")      # v3.3.0+
expect_shape(matrix, c(10, 5))         # v3.3.0+

Sets and Collections

集合

r
expect_setequal(x, y)           # Same elements, any order
expect_contains(fruits, "apple") # Subset check (v3.2.0+)
expect_in("apple", fruits)       # Element in set (v3.2.0+)
expect_disjoint(set1, set2)      # No overlap (v3.3.0+)
r
expect_setequal(x, y)           # 元素相同,顺序不限
expect_contains(fruits, "apple") # 子集检查(v3.2.0+)
expect_in("apple", fruits)       # 元素是否在集合中(v3.2.0+)
expect_disjoint(set1, set2)      # 无重叠(v3.3.0+)

Logical

逻辑判断

r
expect_true(condition)
expect_false(condition)
expect_all_true(vector > 0)      # All elements TRUE (v3.3.0+)
expect_all_false(vector < 0)     # All elements FALSE (v3.3.0+)
r
expect_true(condition)
expect_false(condition)
expect_all_true(vector > 0)      # 所有元素为TRUE(v3.3.0+)
expect_all_false(vector < 0)     # 所有元素为FALSE(v3.3.0+)

Design Principles

设计原则

1. Self-Sufficient Tests

1. 自包含测试

Each test should contain all setup, execution, and teardown code:
r
undefined
每个测试应包含所有设置、执行和清理代码:
r
undefined

Good: self-contained

良好:独立完整

test_that("foofy() works", { data <- data.frame(x = 1:3, y = letters[1:3]) result <- foofy(data) expect_equal(result$x, 1:3) })
test_that("foofy() 正常工作", { data <- data.frame(x = 1:3, y = letters[1:3]) result <- foofy(data) expect_equal(result$x, 1:3) })

Bad: relies on ambient state

不良:依赖全局状态

dat <- data.frame(x = 1:3, y = letters[1:3]) test_that("foofy() works", { result <- foofy(dat) # Where did 'dat' come from? expect_equal(result$x, 1:3) })
undefined
dat <- data.frame(x = 1:3, y = letters[1:3]) test_that("foofy() 正常工作", { result <- foofy(dat) # 'dat' 来自哪里? expect_equal(result$x, 1:3) })
undefined

2. Self-Contained Tests (Cleanup Side Effects)

2. 隔离测试(清理副作用)

Use
withr
to manage state changes:
r
test_that("function respects options", {
  withr::local_options(my_option = "test_value")
  withr::local_envvar(MY_VAR = "test")
  withr::local_package("jsonlite")

  result <- my_function()
  expect_equal(result$setting, "test_value")
  # Automatic cleanup after test
})
Common withr functions:
  • local_options()
    - Temporarily set options
  • local_envvar()
    - Temporarily set environment variables
  • local_tempfile()
    - Create temp file with automatic cleanup
  • local_tempdir()
    - Create temp directory with automatic cleanup
  • local_package()
    - Temporarily attach package
使用
withr
管理状态变更:
r
test_that("函数遵循选项设置", {
  withr::local_options(my_option = "test_value")
  withr::local_envvar(MY_VAR = "test")
  withr::local_package("jsonlite")

  result <- my_function()
  expect_equal(result$setting, "test_value")
  # 测试后自动清理
})
常用withr函数:
  • local_options()
    - 临时设置选项
  • local_envvar()
    - 临时设置环境变量
  • local_tempfile()
    - 创建临时文件并自动清理
  • local_tempdir()
    - 创建临时目录并自动清理
  • local_package()
    - 临时加载包

3. Plan for Test Failure

3. 为测试失败做准备

Write tests assuming they will fail and need debugging:
  • Tests should run independently in fresh R sessions
  • Avoid hidden dependencies on earlier tests
  • Make test logic explicit and obvious
编写测试时假设它们会失败并需要调试:
  • 测试应在全新的R会话中独立运行
  • 避免依赖之前的测试
  • 使测试逻辑清晰明确

4. Repetition is Acceptable

4. 重复是可接受的

Repeat setup code in tests rather than factoring it out. Test clarity is more important than avoiding duplication.
在测试中重复设置代码,而非将其提取出来。测试的清晰度比避免重复更重要。

5. Use
devtools::load_all()
Workflow

5. 使用
devtools::load_all()
工作流

During development:
  • Use
    devtools::load_all()
    instead of
    library()
  • Makes all functions available (including unexported)
  • Automatically attaches testthat
  • Eliminates need for
    library()
    calls in tests
开发期间:
  • 使用
    devtools::load_all()
    而非
    library()
  • 可访问所有函数(包括未导出的)
  • 自动加载testthat
  • 无需在测试中调用
    library()

Snapshot Testing

快照测试

For complex output that's difficult to verify programmatically, use snapshot tests. See references/snapshots.md for complete guide.
Basic pattern:
r
test_that("error message is helpful", {
  expect_snapshot(
    error = TRUE,
    validate_input(NULL)
  )
})
Snapshots stored in
tests/testthat/_snaps/
.
Workflow:
r
devtools::test()                    # Creates new snapshots
testthat::snapshot_review('name')   # Review changes
testthat::snapshot_accept('name')   # Accept changes
对于难以通过编程验证的复杂输出,使用快照测试。查看references/snapshots.md获取完整指南。
基本模式:
r
test_that("错误消息有帮助", {
  expect_snapshot(
    error = TRUE,
    validate_input(NULL)
  )
})
快照存储在
tests/testthat/_snaps/
目录中。
工作流:
r
devtools::test()                    # 创建新快照
testthat::snapshot_review('name')   # 查看变更
testthat::snapshot_accept('name')   # 接受变更

Test Fixtures and Data

测试固件与数据

Three approaches for test data:
1. Constructor functions - Create data on-demand:
r
new_sample_data <- function(n = 10) {
  data.frame(id = seq_len(n), value = rnorm(n))
}
2. Local functions with cleanup - Handle side effects:
r
local_temp_csv <- function(data, env = parent.frame()) {
  path <- withr::local_tempfile(fileext = ".csv", .local_envir = env)
  write.csv(data, path, row.names = FALSE)
  path
}
3. Static fixture files - Store in
fixtures/
directory:
r
data <- readRDS(test_path("fixtures", "sample_data.rds"))
See references/fixtures.md for detailed fixture patterns.
三种测试数据的处理方式:
1. 构造函数 - 按需创建数据:
r
new_sample_data <- function(n = 10) {
  data.frame(id = seq_len(n), value = rnorm(n))
}
2. 带清理的本地函数 - 处理副作用:
r
local_temp_csv <- function(data, env = parent.frame()) {
  path <- withr::local_tempfile(fileext = ".csv", .local_envir = env)
  write.csv(data, path, row.names = FALSE)
  path
}
3. 静态固件文件 - 存储在
fixtures/
目录中:
r
data <- readRDS(test_path("fixtures", "sample_data.rds"))
查看references/fixtures.md获取详细的固件模式。

Mocking

模拟

Replace external dependencies during testing using
local_mocked_bindings()
. See references/mocking.md for comprehensive mocking strategies.
Basic pattern:
r
test_that("function works with mocked dependency", {
  local_mocked_bindings(
    external_api = function(...) list(status = "success", data = "mocked")
  )

  result <- my_function_that_calls_api()
  expect_equal(result$status, "success")
})
使用
local_mocked_bindings()
在测试期间替换外部依赖。查看references/mocking.md获取全面的模拟策略。
基本模式:
r
test_that("函数在模拟依赖下正常工作", {
  local_mocked_bindings(
    external_api = function(...) list(status = "success", data = "mocked")
  )

  result <- my_function_that_calls_api()
  expect_equal(result$status, "success")
})

Common Patterns

常见模式

Testing Errors with Specific Classes

测试特定类别的错误

r
test_that("validation catches errors", {
  expect_error(
    validate_input("wrong_type"),
    class = "vctrs_error_cast"
  )
})
r
test_that("验证能捕获错误", {
  expect_error(
    validate_input("wrong_type"),
    class = "vctrs_error_cast"
  )
})

Testing with Temporary Files

使用临时文件测试

r
test_that("file processing works", {
  temp_file <- withr::local_tempfile(
    lines = c("line1", "line2", "line3")
  )

  result <- process_file(temp_file)
  expect_equal(length(result), 3)
})
r
test_that("文件处理正常", {
  temp_file <- withr::local_tempfile(
    lines = c("line1", "line2", "line3")
  )

  result <- process_file(temp_file)
  expect_equal(length(result), 3)
})

Testing with Modified Options

修改选项测试

r
test_that("output respects width", {
  withr::local_options(width = 40)

  output <- capture_output(print(my_object))
  expect_lte(max(nchar(strsplit(output, "\n")[[1]])), 40)
})
r
test_that("输出遵循宽度设置", {
  withr::local_options(width = 40)

  output <- capture_output(print(my_object))
  expect_lte(max(nchar(strsplit(output, "\n")[[1]])), 40)
})

Testing Multiple Related Cases

测试多个相关案例

r
test_that("str_trunc() handles all directions", {
  trunc <- function(direction) {
    str_trunc("This string is moderately long", direction, width = 20)
  }

  expect_equal(trunc("right"), "This string is mo...")
  expect_equal(trunc("left"), "...erately long")
  expect_equal(trunc("center"), "This stri...ely long")
})
r
test_that("str_trunc() 处理所有方向", {
  trunc <- function(direction) {
    str_trunc("This string is moderately long", direction, width = 20)
  }

  expect_equal(trunc("right"), "This string is mo...")
  expect_equal(trunc("left"), "...erately long")
  expect_equal(trunc("center"), "This stri...ely long")
})

Custom Expectations in Helper Files

辅助文件中的自定义断言

r
undefined
r
undefined

In tests/testthat/helper-expectations.R

在tests/testthat/helper-expectations.R中

expect_valid_user <- function(user) { expect_type(user, "list") expect_named(user, c("id", "name", "email")) expect_type(user$id, "integer") expect_match(user$email, "@") }
expect_valid_user <- function(user) { expect_type(user, "list") expect_named(user, c("id", "name", "email")) expect_type(user$id, "integer") expect_match(user$email, "@") }

In test file

在测试文件中

test_that("user creation works", { user <- create_user("test@example.com") expect_valid_user(user) })
undefined
test_that("用户创建正常", { user <- create_user("test@example.com") expect_valid_user(user) })
undefined

File System Discipline

文件系统规范

Always write to temp directory:
r
undefined
始终写入临时目录:
r
undefined

Good

良好

output <- withr::local_tempfile(fileext = ".csv") write.csv(data, output)
output <- withr::local_tempfile(fileext = ".csv") write.csv(data, output)

Bad - writes to package directory

不良 - 写入包目录

write.csv(data, "output.csv")

**Access test fixtures with `test_path()`:**

```r
write.csv(data, "output.csv")

**使用`test_path()`访问测试固件:**

```r

Good - works in all contexts

良好 - 在所有环境中都能正常工作

data <- readRDS(test_path("fixtures", "data.rds"))
data <- readRDS(test_path("fixtures", "data.rds"))

Bad - relative paths break

不良 - 相对路径会失效

data <- readRDS("fixtures/data.rds")
undefined
data <- readRDS("fixtures/data.rds")
undefined

Advanced Topics

高级主题

For advanced testing scenarios, see:
  • references/bdd.md - BDD-style testing with describe/it, nested specifications, test-first workflows
  • references/snapshots.md - Snapshot testing, transforms, variants
  • references/mocking.md - Mocking strategies, webfakes, httptest2
  • references/fixtures.md - Fixture patterns, database fixtures, helper files
  • references/advanced.md - Skipping tests, secrets management, CRAN requirements, custom expectations, parallel testing
对于高级测试场景,查看:
  • references/bdd.md - 使用describe/it的BDD风格测试、嵌套规格、测试优先工作流
  • references/snapshots.md - 快照测试、转换、变体
  • references/mocking.md - 模拟策略、webfakes、httptest2
  • references/fixtures.md - 固件模式、数据库固件、辅助文件
  • references/advanced.md - 跳过测试、密钥管理、CRAN要求、自定义断言、并行测试

testthat 3 Modernizations

testthat 3 现代化特性

When working with testthat 3 code, prefer modern patterns:
Deprecated → Modern:
  • context()
    → Remove (duplicates filename)
  • expect_equivalent()
    expect_equal(ignore_attr = TRUE)
  • with_mock()
    local_mocked_bindings()
  • is_null()
    ,
    is_true()
    ,
    is_false()
    expect_null()
    ,
    expect_true()
    ,
    expect_false()
New in testthat 3:
  • Edition system (
    Config/testthat/edition: 3
    )
  • Improved snapshot testing
  • waldo::compare()
    for better diff output
  • Unified condition handling
  • local_mocked_bindings()
    works with byte-compiled code
  • Parallel test execution support
使用testthat 3代码时,优先使用现代模式:
已弃用 → 现代:
  • context()
    → 移除(与文件名重复)
  • expect_equivalent()
    expect_equal(ignore_attr = TRUE)
  • with_mock()
    local_mocked_bindings()
  • is_null()
    ,
    is_true()
    ,
    is_false()
    expect_null()
    ,
    expect_true()
    ,
    expect_false()
testthat 3 新增特性:
  • 版本系统(
    Config/testthat/edition: 3
  • 改进的快照测试
  • 使用
    waldo::compare()
    提供更优的差异输出
  • 统一的条件处理
  • local_mocked_bindings()
    支持字节编译代码
  • 支持并行测试执行

Quick Reference

快速参考

Initialize:
usethis::use_testthat(3)
Run tests:
devtools::test()
or Ctrl/Cmd + Shift + T
Create test file:
usethis::use_test("name")
Review snapshots:
testthat::snapshot_review()
Accept snapshots:
testthat::snapshot_accept()
Find slow tests:
devtools::test(reporter = "slow")
Shuffle tests:
devtools::test(shuffle = TRUE)
初始化:
usethis::use_testthat(3)
运行测试:
devtools::test()
或 Ctrl/Cmd + Shift + T
创建测试文件:
usethis::use_test("name")
查看快照:
testthat::snapshot_review()
接受快照:
testthat::snapshot_accept()
查找慢测试:
devtools::test(reporter = "slow")
随机化测试顺序:
devtools::test(shuffle = TRUE)