testing-r-packages について
R パッケージ開発者向けの testthat 3 テストガイドです。テスト構成、期待値、スナップショット、モック、フィクスチャを扱い、個別テストから全体実行までを支援します。
主な機能
- 01testthat 3 の導入とファイル構成
- 02BDD テストと期待値の使い方
- 03スナップショットテストとレビュー
- 04フィクスチャ、モック、副作用の後始末
- 05個別ファイルから全テストまでの実行
活用シーン
- 01R パッケージのテスト構成を整備
- 02関数の結果、エラー、警告を検証
- 03依存先を分離し一時ファイルを管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posit-dev/ skills --skill testing-r-packagesインストール後、Claude Code で/testing-r-packagesを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced.mdbdd.mdfixtures.mdmocking.mdsnapshots.md詳細
testthat を使用した R パッケージのテスト
testthat 3 以降を使用した、R パッケージのテストに関する現代的なベストプラクティスです。
初期設定
testthat 第 3 版でテストを初期化します。
usethis::use_testthat(3)
これにより、tests/testthat/ ディレクトリが作成され、DESCRIPTION の Suggests に Config/testthat/edition: 3 とともに testthat が追加され、tests/testthat.R が作成されます。
ファイル構成
パッケージ構造を反映します:
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()でアクセスする静的なテストデータファイル
テストの構造
テストは 3 段階の階層に従います:ファイル → テスト → 期待値
標準構文
test_that("descriptive behavior", {
result <- my_function(input)
expect_equal(result, expected_value)
})
テストの説明は自然な文章にし、実装ではなく動作を説明してください。
BDD 構文(describe/it)
ビヘイビア駆動開発では、describe() と it() を使用します。
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))
})
})
主な機能:
describe()はコンポーネントに関連する仕様をグループ化しますit()は個々の仕様を定義します(test_that()と同様です)- 階層的に整理するためのネストに対応しています
- コードを含まない
it()は、保留中のテストのプレースホルダーを作成します
適切なものを実装していることを確認するには describe() を使い、適切に実装していることを確認するには test_that() を使います。
包括的な BDD パターン、ネストした仕様、テストファーストのワークフローについては、references/bdd.md を参照してください。
テストの実行
テストには 3 つの規模があります。
マイクロ(対話的な開発):
devtools::load_all()
expect_equal(foofy(...), expected)
メゾ(単一ファイル):
testthat::test_file("tests/testthat/test-foofy.R")
# RStudio: Ctrl/Cmd + Shift + T
マクロ(テストスイート全体):
devtools::test() # Ctrl/Cmd + Shift + T
devtools::check() # Ctrl/Cmd + Shift + E
主な期待値
等価性
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+)
エラー、警告、メッセージ
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())
パターンマッチング
expect_match("Testing is fun!", "Testing")
expect_match(text, "pattern", ignore.case = TRUE)
構造と型
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+
集合とコレクション
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+)
論理
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+)
設計原則
1. 自己完結したテスト
各テストに、セットアップ、実行、後処理のコードをすべて含めます。
# 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)
})
# 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)
})
2. 影響をクリーンアップして自己完結させるテスト
状態の変更には withr を使用します。
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
})
よく使われる withr 関数:
local_options()- オプションを一時的に設定しますlocal_envvar()- 環境変数を一時的に設定しますlocal_tempfile()- 自動クリーンアップ付きで一時ファイルを作成しますlocal_tempdir()- 自動クリーンアップ付きで一時ディレクトリを作成しますlocal_package()- パッケージを一時的にアタッチします
3. テストの失敗に備える
失敗してデバッグが必要になることを想定してテストを記述します。
- テストは新しい R セッションで個別に実行できるようにします
- 先行するテストへの隠れた依存関係を避けます
- テストのロジックを明示的でわかりやすくします
4. 繰り返しは許容されます
共通化するよりも、テスト内でセットアップコードを繰り返します。重複を避けることよりも、テストの明確さが重要です。
5. devtools::load_all() のワークフローを使う
開発中は、次のようにします。
library()の代わりにdevtools::load_all()を使用します- 非エクスポート関数を含むすべての関数が使用可能になります
- testthat が自動的にアタッチされます
- テスト内で
library()を呼び出す必要がなくなります
スナップショットテスト
プログラムによる検証が難しい複雑な出力には、スナップショットテストを使用します。詳しいガイドは references/snapshots.md を参照してください。
基本パターン:
test_that("error message is helpful", {
expect_snapshot(
error = TRUE,
validate_input(NULL)
)
})
スナップショットは tests/testthat/_snaps/ に保存されます。
ワークフロー:
devtools::test() # Creates new snapshots
testthat::snapshot_review('name') # Review changes
testthat::snapshot_accept('name') # Accept changes
テストフィクスチャとデータ
テストデータには 3 つの方法があります。
1. コンストラクター関数 - 必要に応じてデータを作成します:
new_sample_data <- function(n = 10) {
data.frame(id = seq_len(n), value = rnorm(n))
}
2. クリーンアップ付きのローカル関数 - 副作用を処理します:
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/ ディレクトリに保存します:
data <- readRDS(test_path("fixtures", "sample_data.rds"))
フィクスチャの詳しいパターンについては、references/fixtures.md を参照してください。
モック
local_mocked_bindings() を使用して、テスト中に外部依存関係を置き換えます。包括的なモック戦略については、references/mocking.md を参照してください。
基本パターン:
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")
})
よくあるパターン
特定のクラスのエラーをテストする
test_that("validation catches errors", {
expect_error(
validate_input("wrong_type"),
class = "vctrs_error_cast"
)
})
一時ファイルを使ってテストする
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)
})
オプションを変更してテストする
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)
})
関連する複数のケースをテストする
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")
})
ヘルパーファイルでカスタム期待値を定義する
# In 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, "@")
}
# In test file
test_that("user creation works", {
user <- create_user("[email protected]")
expect_valid_user(user)
})
ファイルシステムのルール
必ず一時ディレクトリに書き込みます:
# Good
output <- withr::local_tempfile(fileext = ".csv")
write.csv(data, output)
# Bad - writes to package directory
write.csv(data, "output.csv")
test_path() でテストフィクスチャにアクセスします:
# Good - works in all contexts
data <- readRDS(test_path("fixtures", "data.rds"))
# Bad - relative paths break
data <- readRDS("fixtures/data.rds")
高度なトピック
高度なテストのシナリオについては、以下を参照してください:
- references/bdd.md - describe/it を使った BDD スタイルのテスト、入れ子の仕様、テストファーストのワークフロー
- references/snapshots.md - スナップショットテスト、変換、バリアント
- references/mocking.md - モック戦略、webfakes、httptest2
- references/fixtures.md - フィクスチャのパターン、データベースフィクスチャ、ヘルパーファイル
- references/advanced.md - テストのスキップ、シークレットの管理、CRAN の要件、カスタム期待値、並列テスト
testthat 3 の最新化
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()がバイトコンパイル済みコードで動作- テストの並列実行をサポート
クイックリファレンス
初期化: 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)