mcpField
R
rust-testing-code-review

rust-testing-code-review

github.com/existential-birdsテスト・品質8589 回インストール11 週間前に更新
リポジトリ

rust-testing-code-review について

Rust のテストコードをレビューするスキルです。テスト構成、非同期処理、アサーション、分離性、カバレッジを確認し、edition に応じた検査と検証を経て指摘をまとめます。

主な機能

  1. 01Rust edition に応じたレビュー項目
  2. 02単体テストと統合テストの構成
  3. 03非同期テストとテストダブルの指針
  4. 04アサーション、分離性、エラー経路の確認
  5. 05並行処理と高度なテストのカバレッジ確認

活用シーン

  1. 01Rust crate のテスト構成レビュー
  2. 02非同期テストとテストダブルの確認
  3. 03境界ケースとエラー経路のカバレッジ評価
  4. 04並行処理のテスト手法のレビュー

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/existential-birds/beagle --skill rust-testing-code-review

インストール後、Claude Code で/rust-testing-code-reviewを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
advanced-testing.md
concurrency-testing.md
integration-tests.md
unit-tests.md

詳細

Rust テストのコードレビュー

レビューワークフロー

  1. Rust エディションを確認する — Cargo.toml に記載されたエディションを確認します(2021 と 2024)。エディション 2024 では、if let と末尾の式における一時値のスコープが変更され、リンターの抑制には #[expect] が推奨されるようになります
  2. テストの構成を確認する — 単体テストは #[cfg(test)] モジュールに、統合テストは tests/ ディレクトリに配置します
  3. 非同期テストのセットアップを確認する — 非同期テストには #[tokio::test] を使用し、ランタイムを適切に設定します。ネイティブのトレイト内 async fn を使用できるモックに async-trait が使われていないか確認します
  4. アサーションを確認する — 意味のあるメッセージと、適切なアサーションの種類を使用します。エディション 2024 で一時値のスコープが変更されることを踏まえて、if let アサーションを確認します
  5. テストの分離性を確認する — テスト間で可変状態を共有せず、セットアップとティアダウンを適切に行います。共有フィクスチャには lazy_static!/once_cell よりも LazyLock を優先します
  6. カバレッジのパターンを確認する — エラーパスをテストし、エッジケースを網羅します

ゲート(必須)

各合格条件を満たすまで、出力形式に進まないでください(具体的な成果物を伴う yes/no で判定)。

  1. エディションを記録済み — 対象クレートの Cargo.toml(またはワークスペースの [workspace.package]/継承されたエディション)を開き、edition の値を記録します。合格: Rust 2024 固有の動作(if let/末尾の一時値のドロップ、#[expect] と #[allow] の移行、トレイト内のネイティブな async fn をデフォルトで使うこと)に言及する前に、edition = "…" と引用できるか、「ワークスペースから継承」と記録できていること。エディションが 2024 でない場合、これらの項目をエディション 2024 のリグレッションとして報告してはいけません。有用な場合でも、情報提供にとどめてください。
  2. dyn と静的な非同期モックの比較 — async-trait の代わりにトレイト内のネイティブな async fn を提案する前に、モックが dyn Trait として使われているか確認します。合格: dyn が必要な場合、その提案を見送るか、有効なパターンに従い(async-trait が引き続き必要)、内容を合わせていること。
  3. 検証プロトコル — 合格: 指摘事項を挙げる前に、review-verification-protocol スキルの手順を完了していること(提出前の指摘事項を参照)。

出力形式

次の形式で指摘事項を報告します。

[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.

クイックリファレンス

問題の種類参照先
単体テスト、アサーション、命名、スナップショット、rstest、ドキュメントテスト、#[expect]、LazyLock フィクスチャ、末尾式のスコープreferences/unit-tests.md
統合テスト、非同期テスト、フィクスチャ、テストデータベース、ネイティブな async fn モック、if let の一時値スコープreferences/integration-tests.md
ファジング、proptest、Miri、Loom の基本、モック戦略、stub/fake/mock/spy の分類、rstest マトリクス、paste!、build.rs のテスト生成、criterion のベースラインと black_box の使用方針、trybuild UI テスト、clippy のリンターグループreferences/advanced-testing.md
Loom のインターリーブテスト、Miri による UB チェック、shuttle、ThreadSanitizer、並行コード向け CI マトリクスreferences/concurrency-testing.md

レビューチェックリスト

テスト構造
  • ソースファイル内の #[cfg(test)] mod tests に単体テストを配置する
  • 統合テストは tests/ ディレクトリに配置する(モジュールまたは機能ごとに 1 ファイル)
  • テストモジュールから親モジュールの項目にアクセスするため、use super::* を使用する
  • テスト関数名でシナリオを表す:test_<function>_<scenario>_<expected>
  • テストは独立しており、実行順序に依存しない
非同期テスト
  • 非同期テスト関数に #[tokio::test] を使用する
  • マルチスレッドの動作をテストする場合は #[tokio::test(flavor = "multi_thread")] を使用する
  • 非同期テスト内で block_on を使用しない(直接 .await を使用する)
  • ハングする可能性のあるテストにタイムアウトを設定する
  • モックのトレイトでは async-trait クレートの代わりにネイティブな async fn を使用する(Rust 1.75 から安定版)
アサーション
  • 値の比較には assert_eq! / assert_ne! を使用します(assert! よりエラーメッセージが分かりやすくなります)
  • 自明でないアサーションにはカスタムメッセージを付けます
  • enum のバリアント確認には matches! マクロを使用します
  • エラー型は文字列比較ではなく、matches! またはパターンマッチングで確認します
  • 実用上可能な場合は、1 つのテストにつきアサーションを 1 つにします(失敗の診断が容易になります)
  • if let を使ったアサーションでは、エディション 2024 の一時値のスコープを確認します。条件内の一時値は早くドロップされるため、借用が無効になる可能性があります
  • 末尾式の戻り値について、エディション 2024 の動作を確認します。末尾式内の一時値はローカル変数より先にドロップされます
モックとテストダブル
  • 依存性注入の境界としてトレイトを使用します(具象型ではなく)
  • モック実装は最小限に保ちます。テストに必要なものだけを実装します
  • 所有していない型はモックしません(外部依存関係は独自のトレイトでラップします)
  • テストフィクスチャにはグローバル状態ではなく、ヘルパー関数を使用します
  • 共有テストフィクスチャには lazy_static! や once_cell ではなく std::sync::LazyLock を使用します(Rust 1.80 から安定版)
エラーパステスト
  • 正常系だけでなく、Result::Err のバリアントもテストします
  • 特定のエラーバリアントを確認します(単に「エラーである」ことだけを確認するのではなく)
  • #[should_panic] は必要最小限にし、Result を返すテストを優先します
テストでの lint 抑制
  • テスト固有の抑制には #[allow(lint)] ではなく #[expect(lint)] を使用します(Rust 1.81 から安定版)
  • テストコードのすべての #[expect] または #[allow] に理由のコメントを付けます
  • 自動的に不要になるよう、古い #[allow] 属性を #[expect] に移行します
テストの命名
  • テスト名は動作を説明する文のようにします(test_happy_path のような名前ではなく)
  • 関連するテストは、整理しやすいようにネストした mod ブロックにまとめます
  • テスト名は次のパターンに従います:<function>_should_<behavior>_when_<condition>
スナップショットテスト
  • 複雑な構造化出力(JSON、YAML、HTML、CLI 出力)には cargo insta を使用します
  • スナップショットは小さく、対象を絞ったものにします(巨大なオブジェクトは避けます)
  • 変動するフィールド(タイムスタンプ、UUID など)にはリダクションを使用します
  • スナップショットは snapshots/ ディレクトリにコミットします
  • 単純な値にはスナップショットではなく assert_eq! を使用します
パラメーター化テスト
  • 類似する入力に対するテスト関数の重複を避けるため、rstest を使用します
  • 説明的なパラメーター化テストには、#[case::name] 属性とともに #[rstest] を使用します
  • 複数のテストで同じ構築が必要な場合は、共通のテストセットアップに #[fixture] を使用します
  • パラメーター化テストでは、説明的なケース名を付けます(#[case(1)] のような名前だけにしないでください)
  • 非同期テストと組み合わせる場合は、非同期のパラメーター化テストに #[rstest] #[tokio::test] を使用します
ドキュメントテスト
  • 公開 API 関数には、実行可能なコードを含む /// # Examples を付けます
  • ドキュメントテストは、ドキュメントと正しさの検証の両方の役割を果たします
  • 例を読みやすく保つため、セットアップ用の非表示行には # を付けます
  • cargo test --doc が成功します(nextest はドキュメントテストを実行しません)
並行性テスト

詳細なガイダンス:references/concurrency-testing.md

  • 手書きの atomic/unsafe impl Send|Sync/ステートマシンには、std::sync ではなく loom::sync と loom::thread のシムを使った #[cfg(loom)] テストを用意します
  • atomic、ポインター、または UnsafeCell に触れる unsafe を含むクレートでは、すべての PR に対して CI で cargo +nightly miri test --all-features を実行します(リリース時のみの実行や、モジュールレベルの cfg_attr(miri, ignore) は不可)
  • ロックフリーのデータ構造(AtomicPtr を使うスタック/キュー/リスト)には、loom と Miri(Stacked Borrows および Tree Borrows)に加え、proptest による操作シーケンスを用意します
  • 非決定的な入力(Instant::now、rand、環境、HashMap の反復)は loom::model の本体で使用しません
  • Loom ジョブは LOOM_MAX_PREEMPTIONS に上限を設定し、--release で実行します。loom テストは別の tests/ ファイルに置き、--cfg loom が通常の cargo test に影響しないようにします
  • nextest run -j1 を競合状態の検出範囲を示す証拠として挙げません。FFI を多用する unsafe extern "C" の経路には、ThreadSanitizer ジョブを用意します
テストの拡張(フェイク、モック、スタブ、スパイ)

詳細なガイダンス:references/advanced-testing.md

  • テストダブルは目的に応じて型を使い分けています:スタブ(あらかじめ用意したデータ)、フェイク(動作するが簡略化された実装)、モック(事前に定義した期待値と検証)、スパイ(後から確認できるように呼び出しを記録)— 用語を正確に使っています
  • フェイクの方がシンプルな場合に、mockall 形式のモックを使っていません(リポジトリ形式のトレイトでは、期待値の設定が多いモックよりインメモリ DB が適しています)
  • フェイクは本番実装と同じコントラクトに対してテストしています(トレイト適合テスト)。乖離がありません
  • テストダブルを注入するためにトレイトを境界として使うパターンを採用しています — 本番コードは具象型ではなくトレイトに依存します
パフォーマンステスト(Criterion)

詳細なガイダンス:references/advanced-testing.md

  • 定数畳み込みを防ぐため、ベンチマークでは criterion::black_box(...) を使っています。ポインター形式の入力では black_box(&input) ではなく black_box(input.as_ptr()) を使っています
  • ベンチマークでは I/O を分離しています — セットアップ(ファイルを開く、メモリを割り当てる)は iter_batched のセットアップクロージャー内に置き、計測対象のクロージャー内には置きません
  • CI では cargo bench -- --save-baseline main でベースラインを保存し、フィーチャーブランチでは --baseline main と比較します。回帰のしきい値を定義し、適用しています
  • Criterion のベンチマークは --profile bench でビルドします(リリース最適化と、フレームグラフとの照合に使うデバッグシンボルを有効化)
  • #[bench](不安定で非推奨)は使わず、criterion のみを使います
  • 単発の計測時間を根拠として引用していません。レポートには Criterion の統計エンジンによる平均値と分散を示しています
テスト生成

詳細なガイダンス:references/advanced-testing.md

  • テーブル駆動の入力には #[case::name(...)] を使う rstest を使用しています(テスト出力でわかりやすいケース名を表示)
  • 複数の軸を持つ入力マトリックスには、手作業で展開したテスト関数ではなく、複数のパラメーターで #[values(...)] を使う rstest を使用しています(直積)
  • 大規模なコーパスで入力ごとに名前を付けたテストを作るには、paste! マクロまたは build.rs によるコード生成を使っています(ファイル/ケースごとに #[test] fn を1つ生成)
  • 生成されるテストには、CI ログの差分でも変わらない安定した名前を付けています
Proc マクロの UI テスト(trybuild)

詳細なガイダンス:references/advanced-testing.md

  • コンパイルエラーを出力する Proc マクロには、各失敗パスを .stderr の参照ファイルで網羅する trybuild::TestCases があります
  • 最新の rustc 更新後に .stderr の出力を再生成しています(そうしないと、CI で誤検知による失敗が発生します)。TRYBUILD=overwrite cargo test を CONTRIBUTING に記載しています
  • trybuild のジョブでは、CI で特定の stable rustc を固定しています(.stderr の形式は stable リリース間で変わります)
  • trybuild のテストは nightly ではスキップします(nightly の診断は stable と異なります)
Clippy lint グループの方針

詳細なガイダンス:references/advanced-testing.md

  • clippy::correctness は deny にしています(常に適用します。これらはバグです)
  • ライブラリでは clippy::suspicious を warn または deny にしています
  • clippy::perf を warn にしています(ホットパスで実際に効果があります)
  • ライブラリでは clippy::pedantic を有効にしています。抑制には、単独の #[allow] ではなく、理由を示した #[expect(clippy::lint_name, reason = "...")] を使います
  • CI では clippy::nursery を有効にしていません(実験的で、rustc のリリースごとに変更されます)
  • clippy::restriction lint は個別にオプトインします。グループ全体を有効にすることはありません

重大度の基準

重大
  • テストは成功するものの、実際には動作を検証していない(誤った値に対するアサーション)
  • テスト間で可変状態を共有しているため、結果が不安定になる
  • セキュリティ上重要なコードのエラーパスのテストがない
重大
  • expected メッセージのない #[should_panic](誤ったパニックを含む、あらゆるパニックを捕捉します)
  • テストのセットアップで使われ、実際の失敗箇所を分かりにくくする unwrap()
  • 実行順序に依存するテスト
  • エディション 2024 の一時値のスコープ規則により問題が発生する、アサーション内の一時値を使ったインラインの if let
  • プロジェクトがエディション 2024 を対象としており、トレイトでネイティブの async fn が利用可能な場合に、モックトレイトで使用される async-trait
軽微
  • 複雑な比較でアサーションメッセージがない
  • assert_eq!(x, y) ではなく assert!(x == y) を使っている(エラーメッセージが分かりにくくなります)
  • シナリオを説明していないテスト名
  • ヘルパーに抽出できる冗長なセットアップコード
  • 自動で解消される抑制を実現できる #[expect] ではなく #[allow] を使用している
  • LazyLock が利用可能なのに、テスト用フィクスチャで lazy_static! または once_cell を使用している
参考情報
  • proptest または quickcheck を使ったプロパティベーステストの追加提案
  • 複雑な出力に対するスナップショットテストの追加提案
  • カバレッジを改善できる機会

有効なパターン(指摘しないでください)

  • テスト内の unwrap() / expect() — 予期しないエラーでパニックするのは、テストとして正しい動作です
  • テストモジュール内の use super::* — 親の項目にアクセスする標準的なパターンです
  • テストヘルパーに付ける #[allow(dead_code)] — ヘルパー関数はすべてのテストで使われるとは限りません
  • テスト内の clone() — パフォーマンスより明瞭さを優先します
  • 大きなテスト関数 — 統合テストは長くなることがあり、ヘルパーへの抽出で常に明瞭になるとは限りません
  • ブール値のチェックに使う assert! — 式が明らかにブール値である場合(.is_some()、.is_empty())は問題ありません
  • 1つの論理的な動作を検証する複数のアサーション — 1つの動作に複数のチェックが必要な場合があります
  • Result を返すテスト関数での unwrap() — ? で伝播させる方法も問題ありませんが、必須ではありません
  • dyn ディスパッチが必要なモックトレイトでの async-trait — トレイトのネイティブな async fn は dyn Trait をサポートしないため、この場合は引き続き async-trait が必要です
  • テストヘルパーの正当な理由を添えた #[expect] — 自動で解消される lint 抑制は、テストコードとして適切です
  • 高コストな共有テスト用フィクスチャでの LazyLock — スレッドセーフな遅延初期化は、テスト用グローバル変数に適しています

指摘を報告する前に

問題を報告する前に、review-verification-protocol スキルを読み込み、その指示に従ってください。

コメント