mcpField
R
rust-testing

rust-testing

github.com/rewrite-rsテスト・品質1656 回インストール12 週間前に更新
リポジトリ

rust-testing について

rust-testingは、検証する振る舞いに応じたテスト方法の選択を支援するRust開発者向けスキルです。一般的なテスト形式、ゴールデンファイルテスト、移植時の差分テストに加え、決定性と検証の指針を扱います。

主な機能

  1. 01標準的な5種類のテスト形式を選択
  2. 02テーブル駆動のケースと明確な失敗情報
  3. 03エラーのバリアントと独立した期待値を検証
  4. 04決定性を保ちネットワークを使わないテスト
  5. 05Rust移植に差分テストを適用

活用シーン

  1. 01Rustの変更に合うテスト形式を選ぶ
  2. 02エラー動作の回帰テストを追加
  3. 03Rust移植版を既存実装と比較

インストール

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

$npx skills add https://github.com/rewrite-rs/skills --skill rust-testing

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

同梱ファイル

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

DIFFERENTIAL-TESTING.md
TEST-DESIGN.md
agents/
openai.yaml

詳細

Rust テスト

テストスイートは、チームがどのような変更を黙って受け入れないかを示すものです。このスキルでは、何をどの形式でテストする価値があるかを決めます。通常の5つの形式、ファイル形式の成果物に対するゴールデンファイル、移植に対する差分テストが対象です。テスト対象のコードをどのような形にすべきかは決めません。コードの形を整えるのはクラフトスキルの役割です。変更をテストするかどうかをレビュー判定として決めるのは /rust-code-review です。

テストの目的

テストは、動作が変わったときに失敗するために存在します。実装を言い換えただけのテスト――たとえば、ゲッターが渡されたフィールドを返すと確認するテスト――は、コードがリファクタリングされたときに失敗し、動作が壊れたときに通ります。これはまさに本末転倒です。テストを書く前に、そのテストが検出する回帰を一文で言い表してください。その文が「コードが変わった」なら、そのテストは書かないでください。

何を検証するかに応じて形式を選ぶ

形式は習慣ではなく、検証内容に合わせて選びます。各形式の詳細――ファイルの配置場所、アクセスできる範囲、コスト――については TEST-DESIGN.md を参照してください。各形式の要点は次のとおりです。

形式使用する場面
#[cfg(test)] mod tests 内のユニットテスト非公開の要素にアクセスする必要がある場合、または興味深い境界条件を持つ純粋関数をテストする場合
tests/ 内の統合テスト実際の呼び出し元が目にする公開 API を検証する場合――利用者と同じようにクレートをインポートします
ドキュメントテスト例が何よりもまずドキュメントである場合。実質的にはユニットテストのドキュメントテストは mod tests に置きます
プロパティテスト(proptest)入力空間全体で成り立つ規則を検証する場合――往復変換、不変条件、パーサーとシリアライザーの組み合わせ
スナップショットテスト(insta)出力が大きく、構造化されていて、目視でレビューする場合――レンダリング済みテキスト、生成コード、エラーレポート

その帰結として、本番環境で見つかったバグには、そのバグが存在したレベルのユニットテストを追加します。たまたまそのバグをカバーするエンドツーエンドテストではありません。

コピー&ペーストよりテーブル駆動

1つのテスト、ケースのテーブル、1つのループで記述します。

#[test]
fn parses_durations() {
    let cases = [
        ("1s", Some(Duration::from_secs(1))),
        ("500ms", Some(Duration::from_millis(500))),
        ("", None),
        ("1x", None),
    ];
    for (input, expected) in cases {
        assert_eq!(parse_duration(input), expected, "input: {input:?}");
    }
}

テーブルの効果を高めるのはメッセージです。"input: {input:?}" がなければ、3番目のケースの失敗は行番号しか報告せず、どのケースが失敗したかはわかりません。ケースごとに固有のテスト名と失敗行が必要な場合は、rstest で同じテーブルからケースごとのテストを展開します。

失敗も含めて動作を検証する

成功パスだけをカバーするテストスイートはエラー時の動作を何も示さないため、エラー型がどのように変わってもテストに通ってしまいます。呼び出し元がマッチするエラーのバリアントを検証してください。Display 文字列ではありません。文字列は契約ではありません。どのバリアントが存在するかについては /rust-errors を参照してください。

期待値の計算に、テスト対象と同じロジックを使うテストは何も検証していません。見落としやすい問題です。一見すると十分なテストに見えます――入力をループしてテスト対象の関数で期待値を組み立てたり、同じ計算式から定数を求めたりするからです。期待値は人が記述するか、参照実装、公表されたテストベクトル、手計算した例など、独立した情報源から取得してください。

決定性はマシンではなくテストの特性

すべてのジェネレーターに明示的なシードを設定し、時計を直接読み取るのではなく時刻と乱数を注入してください。また、ユニットテストや統合テストでネットワークに触れないでください。不安定なテストは、不安定さが現れた日に削除するか修正します。不安定なテストを1つでも許容するテストスイートは、読み直す代わりに再実行することを皆に教えてしまいます。

カバレッジは指標であり、目標ではない

cargo llvm-cov はテストする価値のある分岐を見つけるのに役立ちます。パーセンテージによるゲートは、検証済みの動作ではなく、行に触れるだけのテストを生み出します。数値を追いかけないでください。

移植版の差分テスト

Rust 実装で既存の実装を置き換える場合、最も強力な証拠は、同じ入力で両方を実行して結果が一致することです。3 種類のハーネス(記録済みコーパス、並列実行、共通ジェネレーターを使った proptest)とそれぞれのトレードオフ、差分の正規化と記録のルールについては、DIFFERENTIAL-TESTING.md を参照してください。許容される差分を定めるパリティ契約は /port-to-rust の担当です。このスキルが提供するのは仕組みであり、契約ではありません。

先送りする項目

エラーのバリアント設計については /rust-errors を参照してください。unsafe コードのテストには Miri を使います。詳しくは /unsafe-rust を参照してください。非同期テストの種類、時間の制御、実時間のスリープを使う #[tokio::test] がなぜ不安定なテストを生みやすいのかについては /async-rust を参照してください。インターリーブを期待するのではなく探索する並行性テストには loom を使います。詳しくは /rust-concurrency を参照してください。本当はベンチマークであるテストには criterion を使います。詳しくは /rust-performance を参照してください。レビューの判定として、変更にテストがそもそも必要かどうかについては /rust-code-review を参照してください。

検証

cargo test --all-features
cargo test --doc
cargo clippy --all-targets --all-features   # add -- -D warnings only if the repo has no lint config

Clippy は、リポジトリで設定されている lint レベルで実行します。clippy.toml や独自の lint 属性があるリポジトリでは、それらを維持し、より厳しいレベルを上乗せすることはありません。

新しいテストは、採用する前に失敗することを確認する必要があります。修正をコメントアウトするか、アサーションを反転させて、失敗メッセージが実際の問題を示していることを確認してください。一度も失敗を確認していないテストは、何もアサートしていない可能性があります。

コメント