組込みユニットテスト(仕様ベース)
ユーザーから提供された .c/.h ファイル群に対して、Maurício Aniche(『Effective Software Testing』第2章)の仕様ベーステストの7ステップに従い、ユニットテストスイートを生成します。このスキルを価値あるものにする判断基準は次の通りです。各関数のドメインを適切に分割する(最も価値のあるステップ)、境界を導出し、バグを捕捉できる最小限のスイートを設計し、プロジェクトの適切なフレームワークで自動化し、カバレッジ目標に達するまで反復することです。
フェーズ3の前に references/specification-based-testing.md を読んでください。これは、分割と境界の考え方や、肥大化や脆弱性を回避するための実践的な方法を調整するためのものです。
スコープの不変条件(最初に読んでください)
- 英語出力。 このスキルが生成するものはすべて英語です。ユーザーが別の言語で記述した場合でも、テストコード、テスト名、コメント、カタログ、
Covers:注釈、スキップ/無視メッセージ、パス宣言、最終サマリーは英語です。 - ユニットテストのみ。 統合テスト、E2Eテスト、システムテスト、HILテスト、Robot Frameworkテストは絶対に生成しません。粒度は独立した関数/モジュールであり、依存関係はフェイク/モックで代用します。
- 指定されたファイルのみ。 テストスイートは、ユーザーが指定した
.c/.hファイル群のためのものです。プロジェクトをスキャンして他のターゲットを見つけたり、隣接するモジュールを独自にテストしたりしないでください。 - 対象ファイルを絶対に変更しない – 一時的であっても、「テストして元に戻す」ためであっても変更しません。テストスイートは元の
.cファイルをコンパイルし、テストディレクトリにコピーは作成しません。困難な部分に到達するには、公開API、依存関係のフェイク、または別のテストバイナリを使用します。 - 自律的に動作します。 実行の確認を求めずに、生成、実行、報告を行います。2つの例外があり、その場合は停止して質問します。(a)パスC(ZephyrでもESP-IDFでもない)でユニットテストフレームワークを特定できない場合(フェーズ0)。(b)カバレッジ目標に達するまでに2回のカバレッジ反復を経過した場合(フェーズ8)。
- あなたはレポートを書きません。 結果レポートは作成しません。ツールを実行し、それらが生成する成果物を指し示します(カバレッジHTML、ランナー出力)。「結果」はツールが出力するものであり、あなたの散文ではありません。
- カバレッジ目標: デフォルトでは関数 ≥ 95%、行 ≥ 95%、分岐 ≥ 90%(95/95/90)。目標に達したら反復を停止し、未カバーの行/分岐をリストします。ユーザーが明示的に要求した場合にのみ、100/100/100を目指します。目標を下回るのは、欠落している部分が証明可能な範囲で到達不可能な場合のみとし(ログ/アサートノイズ、モジュールのテスト容易性の制限)、その場合は残りの各行/分岐を正当化します。
フロー(Anicheの7ステップ → 10フェーズ)
7つのステップは厳格なパイプラインではなく、反復的です(フェーズ3で、しばしばフェーズ1に戻るきっかけとなる何かが明らかになります)。以下のフェーズが骨格です。
| フェーズ | Anicheのステップ | あなたの作業 |
|---|---|---|
| 0 | — | 環境検出 → パスA(Zephyr)、B(ESP-IDF)、C(その他) |
| 1 | 1 理解 | 各関数の要件、入力、出力を理解 |
| 2 | 2 コード調査 | 依存関係をマッピングし、フェイク/モックで分離 |
| 3 | 3 分割 | 分割の番号付きカタログ(最も重要なステップ) |
| 4 | 4 境界 | 境界の番号付きカタログ(オン/オフポイント) |
| 5 | 5 ケース設計 | 分割/境界を組み合わせ、組み合わせを削減 |
| 6 | 6 自動化 | パスのフレームワークでスイートを作成(トレーサビリティ付き) |
| 7 | — | ツールを実行:実行 + カバレッジ |
| 8 | 7 拡張 | ギャップを反復(カバレッジ + テストのないカタログアイテム) |
フェーズ0 — 環境検出
指定されたファイルが含まれるプロジェクトの環境を、以下の順序で特定し、パスを選択します。Robot Framework、HIL、統合pytestはユニットテストフレームワークではありません – これらはどのパスでもカウントされません。
-
Zephyr?→ パスA。 信号:
west.yml/west.yaml;find_package(Zephyr ...)を含むCMakeLists.txt;prj.conf;testcase.yaml/sample.yaml;tests/;ztest/ZTEST_SUITE。ツール:twister + ztest + FFF + カバレッジ。references/zephyr-path.mdを読んでください。 -
ESP-IDF?→ パスB。 信号:
idf_component_register(を含むCMakeLists.txt;sdkconfig/sdkconfig.defaults;$ENV{IDF_PATH}/idf.pyの使用;main/コンポーネント。ツール:ESP-IDFのネイティブUnity(ホストビルド、linuxターゲット)+ gcov/gcovr。Unityを直接使用してください – 停止して質問しないでください。 例外:プロジェクトがすでに別のユニットテストフレームワーク(Ceedling、GoogleTest…)を一貫して使用している場合は、そちらに従います。references/espidf-path.mdを読んでください。 -
その他(ZephyrでもESP-IDFでもない)→ パスC。 既存のユニットテスト構造(Unity、CMock、Ceedling、GoogleTest、カスタム)を調査し、プロジェクトのパターンに忠実に従います。ユニットテストフレームワークが見つからない場合は、停止してユーザーにどれを使用するか質問します – パスCでは決して暗黙のデフォルトを想定しないでください。
references/non-zephyr-path.mdを読んでください。
応答テキストに記載します(推論だけでなく、具体的な信号と、ファイル名とそこで見つけた内容を挙げて選択したパスを記載します)。例:「パスA(Zephyr):zephyr/west.yml、tests/.../CMakeLists.txt 内の find_package(Zephyr)、tests/lib/ 内の ZTEST_SUITE スイート。」
フェーズ1 — 要件、入力、出力を理解する
スイートがカバーする各関数について、.c/.h を読んで以下を抽出します。
- ビジネスルール: 何をするか、何をしてはいけないか(コーナーケースを含む)
- 入力: 型、範囲/ドメイン、変数間の制約
- 出力: 入力がどのように結果になるか(および観測可能な副作用:出力の書き込み、エラーの返却、モーションの出力/非出力)
これらの一部は明示的であり、一部は推測する暗黙的なドメインルール(飽和、ラップ、単位、符号、バックラッシュ)です。これらの暗黙的なルールは、バグが潜む場所です。
フェーズ2 — コードを調査する
.c/.h 全体を読み、分離すべき依存関係のマップを作成します:HAL、ドライバ、カーネル/RTOS、I/O、時間、他のモジュールへの呼び出し。各依存関係はフェーズ6でフェイク/モックになります。また、実際のロジックに到達する方法を特定します(直接の公開API、またはハンドル/vtableを介した static 関数 – 実際のAPIを通じて呼び出し、シグネチャを発明しないでください)。
フェーズ3 — 分割を特定する(最も重要なステップ)
references/specification-based-testing.md の3つのレンズを各関数に適用します:各入力の個別、入力の組み合わせ、出力クラスです。NULL/空と暗黙的なドメインルールを考慮します。関数に状態がある場合は、モジュール/コンテキストの状態によっても分割します。
関数ごとに、スイート内で安定したIDを持つ名前付きの番号付きカタログを生成します。
- 分割:
<func>/P1, P2, … - 境界(フェーズ4で記入):
<func>/B1, B2, …
各項目に短い説明を付けます。このカタログはスイートのトレーサビリティ契約(フェーズ6で具現化)であり、ギャップ(フェーズ8)の真実の源泉です。適切に行われたフェーズ3は、最初のカバレッジパスでほぼすべてをカバーするはずです。
フェーズ4 — 境界を分析する
隣接する分割間の各境界について、カタログにオンポイント(条件内の値)とオフポイント(反対側の分割の最も近い隣接値)を追加します。等価性の場合は、1つのオンポイントと2つのオフポイントを与えます。すでに他のケースでカバーされている境界は削除します – 重複させないでください。「分割か境界か?関係ありません」:重要なのはケースが存在することです。
フェーズ5 — テストケースを設計する
入力カテゴリ間で分割を組み合わせますが、組み合わせ爆発を削減します:例外的なケース(NULL/空)は1回テストし、すべてと組み合わせないでください。ドメインを使用して削減します。単一のシード入力を使用し、ケースごとに最小限の変更のみ行います(デバッグを容易にするため)。迷った場合は、最も単純な入力を使用します。動作に関与しない入力には、妥当で現実的な値を使用します。ケースのスケルトンが他のいくつかと同じ場合は、パラメータ化として計画します。ケースをリストし、各ケースでどのカタログIDを実行するかを示します。
フェーズ6 — 自動化(必須のトレーサビリティ付き)
検出されたパスのフレームワークでスイートを記述します(構文、分離、スキャフォールドの詳細は references/zephyr-path.md、references/espidf-path.md、references/non-zephyr-path.md を参照)。
フェーズ3/4のカタログは、スイート内の2つのレベルでのコメントになります。
- 関数ごとのヘッダー — その関数のテストセクションの先頭にあるブロックで、完全なカタログ(各分割/境界とそのIDおよび短い説明)をリストします。
- テストごとの注釈 — 各テストは、それが実行するアイテムを参照します。例:
/* Covers: P3 + B1 */。
必須ルール(これがトレーサビリティを価値あるものにします):
- 孤立テストなし: すべてのテストは ≥ 1 つのカタログアイテムを参照します。
- 孤立アイテムなし: すべての分割/境界は ≥ 1 つのテストで実行されます。テストがないアイテムはギャップとしてフラグされます(フェーズ8に供給されます)。
- ヘッダーと注釈間の安定した同一のID: これにより、`grep