テスト設計
テストケースが、スイートが何を捕捉できるかを決定します。 その後のすべて—テストハーネス、ランナー、 カウント—は、その決定を実行するだけです。
これが置き換える瞬間
| ~しようとしていること | 代わりにこうする |
|---|---|
| ハッピーパスを書いて終了する | 入力空間を歩く:各パラメータについて、そのクラスとエッジを考える |
| 実装からテストケースを導出する | 仕様から導出し、実装は驚きとして受け入れる |
| 安全な値でテストする | 各境界の内側の最後の値と外側の最初の値をテストする |
| 仕様が挙げた境界だけをテストする | すべての範囲には両端がある;文が除外した方も依然として境界である |
| 仕様が列挙するロールを列挙する | 仕様が忘れた呼び出し元を追加する:ロールなし、未認証、未割り当て |
| ハッピーパスから一つの条件を変える | 決定表を作成し、二つの条件が同時に失敗するルールをカバーする |
| 状態表を描いて次に進む | すべての行にテストIDを入れる;特に「許可されない」という行に |
| 期待結果として「拒否、または~が提供されれば受け入れ」と書く | 一つのケースに一つの結果;仕様が決定できないなら、それは未解決の質問であり、テストではない |
| エラーパスを可能性が低いとしてスキップする | 各失敗モードはクラスである:無効な入力、リソース欠落、拒否、タイムアウト、途中完了 |
| パスを実装せず、テストなし、スキップマーカー、誰もアサートしないスタブを残す | そのパスが「今日は未実装」として拒否されることをテストする;今はパスし、誰かがビルドした日にレッドになる(下記参照) |
| 主フローで機能をテストする | ユースケースから代替フローと例外フローを読み取る;それぞれがクラスである |
| スイートを読んで適切と評価する | 仕様と照らし合わせてカウントする:クラス、両方の境界、ルール、遷移 |
| カバー済みのクラスに5番目のテストを追加する | カバーされていないクラスに使う—5番目はランタイムを消費し、カバレッジは追加しない |
実装ではなく仕様を攻撃する
実装を読んで書かれたテストは、コードが何をするかをアサートします。 それは到着時にパスし、現在のバグを期待値としてエンコードし、 変更後の失敗は「コードが変わった」と言うだけであり、 それはすでに分かっていることです。 これは推測ではなく測定されています:24のJavaリポジトリにおいて、 LLM生成されたオラクルは、期待される動作よりもプログラムの実際の動作を捉える傾向がありました(Konstantinou, Degiovanni, Papadakis, arXiv:2410.21136)。
それがすべきこと—仕様、 ワイヤーフォーマット、コントラクト—から導出し、その後でのみ実行します。 唯一正当な例外は、現在の動作を意図的に固定する特性テストであり、 それは後で誰かが固定されたバグを要件と誤解しないように、そのようにマークされます。
有用な兆候:すべてのテストが初回実行でグリーンだった場合、 意図が何であれ、スイートは実装から導出されたものです。
等価クラス:テストではなくクラスをカウントする
コードが区別できない入力は一つのクラスを形成し、 クラスあたり一つのテストで十分です—二番目から五番目はランタイムを消費し、何も新しいことを証明しません。 この規律は双方向に働きます:カバーされていないクラスがないこと、 偶然に二回カバーされるクラスがないこと。
17のテストからなるスイートは、網羅的に見えます。 それが実際にそうかどうかは、それらの17のテストがいくつのクラスに分類されるかに依存し、 その数はランナーの出力には表示されません。 それは入力空間に対して手作業でカウントされなければなりません—それがテスト計画です。
仕様が列挙するクラスは簡単な半分です。 三つの行を持つロールテーブルには四つ目のクラスがあります、 それらのどれにも一致しないリクエストであり、 それはテーブルから構築されたスイートには決して含まれないものです。 各パラメータについて、テストケースを書く前に、有効なクラスの隣に無効なクラスを書きなさい: 間違った型、欠落、リストされたすべての値の外側、 権限を全く持たない呼び出し元。
シラバスはこれを習慣ではなくルールとして述べています: 等価分割において、カバレッジ項目はパーティションであり、 100%は識別されたすべてのパーティションが「無効なパーティションも含めて」実行されたことを意味し、 パーティションは「重複してはならず、空集合であってはなりません」(ISTQB CTFL v4.0.1 §4.2.1)。 複数のパラメータがある場合、それは最も弱い基準であるEach Choice—各パラメータのすべてのパーティションを一度ずつ、 組み合わせは無視—を名前としており、これはほとんどのスイートがその名前を知らずに到達するレベルであり、 以下の決定表が始まるところです。
境界、なぜならそこに欠陥があるから
1ずつの誤差は最も一般的な算術的欠陥であり、
それはエッジを除いてどこでも見えません。
各境界で重要なペアは、境界値とその線を挟んだ最も近い隣人であり、
これは2値BVAです(CTFL v4.0.1 §4.2.2);
3値BVAは内側の隣人も追加し、
シラバスはそれが何をもたらすかを示しています:
x ≤ 10を意図したがx = 10と書かれたチェックは、10と11の両方をパスし、
9だけがそれを捕捉します。
繰り返し現れるエッジ:空、ちょうど一つ、多数;ゼロと負;
宣言された最大値とその一つ先;
バッファ、制限、タイムアウトとちょうど同じ値。
このマシンからの具体的なペア: 8,190文字で問題なく、8,192文字で失敗しなければならないPATHチェックは、これら二つの値でテストされなければなりません。 4,000と12,000でパスしても、それらの間の線についてはほとんど何も確立しません。
境界が表明された前提条件である場合、
その先の値は、期待結果を持つケースではなく、呼び出し元のバグです;
design-by-contractは、特定のエッジがどちらであるかを決定し、
テストは該当する方をアサートします。
仕様が名前を挙げなかった側
「最大10 MB」は範囲の一端を挙げています。 もう一端は、文が言及するかどうかにかかわらず存在し、 テキストから導出されたケースはテキストが止まるところで止まります: 上限を−1、0、+1バイトでテストし、1,024バイト以下を何もテストしないスイートは、半分の範囲に対して境界分析を行ったに過ぎません。 定義には側面はありません: 「パーティションの最小値と最大値がその境界値です」(CTFL v4.0.1 §4.2.2)。 すべてのパーティションについて、最初に両方を書き、 次にそれぞれを隣接パーティション内の最も近い隣人と一緒に取ります。 名前の挙がらない側は通常、ゼロ、空、または一つであり、 仕様は通常それについて沈黙しています。なぜなら誰も決定しなかったからです。
二つの条件を同時に動かす
ハッピーパスから一つの条件を変えて構築されたスイートは、すべての条件のすべての値をカバーしますが、それらの組み合わせはほとんどカバーしません: フォーマットが間違っている、サイズ超過、カウント超過、それぞれ単独で、 そして二つが同時に失敗するケースはありません。 両方が該当する場合にどのエラーが勝つかは、仕様にもほとんど書かれていません、 したがって、欠落したケースは欠落した決定を隠しています。
条件を決定表としてレイアウトし、ルールを数え、
次に二つの条件が同時に失敗するルールと、ロール制限とフォーマット制限の両方が適用されるルールをカバーします。
決定表テストでは、カバレッジ項目は実現可能な組み合わせを保持する列であり、
100%はそのようなすべての列が実行されたことを意味します(CTFL v4.0.1 §4.2.3);
シラバスは、表が「要件のギャップや矛盾を見つけるのに役立つ」と付け加えており、
それは上記の欠落した決定が自然に浮かび上がることを意味します。
同じ文書は、Each Choiceの下では完全にカバーされていると読め、列の下では4分の1しかカバーされていないと読め、
二番目の数字だけが組み合わせについて何かを言います。
ルール数がフラグセットの積である場合、
より安上がりの修正は上流にあります:
state-firstはフラグを発生し得る状態に collapsing し、
表はそれらに縮小します。
表はスイートに関する主張である
状態表、決定表、ユースケースのフローリスト: 各行は、その行に対するテストが存在することを主張します。 テストIDを行に入れてください。 IDのない行はカバーされていないクラスであり、 「許可されない」と読める行が最も空である可能性が高いものです、 なぜなら禁止を書くことがそれを処理したように感じられるからです。 禁止された遷移—一つが進行中に二つ目を送信する—がテストであり、 それを停止するはずのガードがテスト対象です。
状態表はその行を明示的にします: 無効な遷移は空のセルであり、 シラバスが定義する三つの基準の中で最も強い、 全遷移カバレッジは、それらのすべてを試みることを要求します—テストケースごとに一つずつ、 一つの欠陥が別の欠陥を隠さないようにします(CTFL v4.0.1 §4.2.4)。 ほとんどのスイートが止まる0-switchである有効遷移カバレッジは、 それらのセルに決して触れず、 それが未テストの「許可されない」行がグリーンのままである理由です。
次に、二つの文書を相互に読み合わせます。 テストのパスが表と矛盾する場合(表は失敗→送信中と言い、テストは失敗→選択→送信に行く)、それらの一つが間違っていることを意味し、 比較以外にどちらかを見つける方法はありません。 自己遷移と障害状態からの遷移も行としてカウントされます。
ケースごとに一つの結果
「拒否、または受け入れられた場合、送信後に破損したデータが残らない」は、パスまたは失敗することができません。 それは正直に現れます、仕様がゼロバイトファイルに何が起こるかを決して言わず、著者が答えを発明することを拒否した場合であり、 ケースが視覚的に存在するため、レビューを生き残ります。 その分離は期待される列に隠れています。 正しい形式は、仕様の所有者への質問であり、 そのようにリストされ、そのクラスに対するテストは答えが出るまでありません。
エラーパスもクラスである
不幸なパスは、テストされていないコードが蓄積される場所です、 なぜなら通常の使用ではそれらを実行するものがないからです—ロック保持者が途中で死ぬ、 存在しないマップされたフォルダ、 期待されるフィールドが欠落した入力。 それぞれは、任意の入力範囲と同じテストへの権利を持つ等価クラスです。 すべてが協力するときにのみツールが動作することを証明するスイートは、デモをテストしたのであり、ツールをテストしたのではありません。
ユースケースはコードが存在する前にクラスを列挙する
ユースケースは、すでにクラスにカットされた仕様です:一つの主フロー、 各代替フロー、各例外フロー、 そしてそれぞれが入力され得る事前条件。 それを書いた人は、このスキルが要求する列挙を行いました—代替フローと例外フローはエラーパスです—したがって、主フローを実行するスイートは、文書がリストするN個のクラスのうちの一つをカバーしたことになります。
ユースケースからフローを読み取り、それぞれにテストを与え、 次に事前条件をチェックします、 なぜならフローはその事前条件が成立できる場合にのみ到達可能だからです。 翻訳ページのパリティチェックには、主フロー(両方の言語が編集されコミットされる)、 代替フロー(例外として宣言された片側編集)、 および事前条件が書き留められた後にのみ現れる二つの例外フローがあります: ツリーがダーティであるため、まだ陳腐性を判断できない; 履歴が書き換えられたため、宣言されたコミットがもはや解決できない。 両方とも一週間以内に一つのサイトでヒットしました。 ハッピーパスだけではどちらも示さなかったでしょう。
例外フローが実際に発生するかどうかは、それがどれほど可能性が低いと感じられるかによって決まるわけではありません。
動作エンベロープは何が範囲外かを言い、ハザード分析は何が残るかを言います;
両方ともassurance-caseに存在し、
このスキルは、直感で刈り込む代わりに、それらが生成するリストを採用します。
カバレッジは名前の付いた基準のもとでの数値である
一つのテスト文書が三つの方法で測定されました:条件の100%、MC/DCの90.9%、
決定ルールの25%。
三つすべては同じファイルに対して真であるため、
基準のないカバレッジ数値は何も言わず、
カウント後に基準が選択された数値はさらに少なく言います。
カウントする前に名前を付けなさい、
シラバス自身の用語で、数値がチェック可能になるように:
パーティションカバレッジとEach Choice(§4.2.1)、
2値または3値BVA(§4.2.2)、決定表の列(§4.2.3)、
すべての状態、有効遷移または全遷移(§4.2.4)。
MC/DCはFoundationシラバスにはありません;
それは安全規格(DO-178C、ISO 26262)から来ており、
そう言わずに他のものと並べてリストすることは、厳密さとして読まれる種類の混合引用です。
数値が読者に負っている残りはmeasured-claimsにあります。
スイートをチェックする:判断するのではなくカウントする
スイートが適切かどうかを尋ねると、それを生み出した盲点が返ってきます。
同じモデルでの二回目のセッションはそれらを共有し、
同じ人が一日後に行う場合も同様です。
シラバスはそれを「ある程度の独立性」と呼び、
その四つのレベルの二番目であり、価値がどこから来るかを言います:
「著者とテスターの認知バイアスの違い」(CTFL v4.0.1 §1.5.3)。
同じモデルでの二回目のセッションはバイアスを共有し、ラベルだけを保持します。
それらを共有しないステップはカウントです:
仕様からクラスをリストし、テストがあるものにチェックを入れ、
すべての範囲の両方の境界をリストし、決定表のルールをリストし、
すべての状態表の行をリストし、
各ギャップのファイルと行を引用します。
期待値が正しいかどうかは判断のまま残り、それを書かなかった読者に委ねられます;
クラスにテストがあるかどうかは算術です。
カウントが網羅的ではなくサンプリングされた場合、
レポートはサンプリングされたと言います。
評決の反論デフォルト形式はadversarial-verifyにあります;
このスキルは何をカウントするかを提供します。
リクエストがクラスを削除するとき
「ボタンが無効になっているため、二重送信は発生しません。その行を削除してください」は、一つのレイヤーでの一つのガードの強度でスイートからクラスを削除します。 要求されたことを行い、一文でどのクラスがカバーされなくなり、何がそれに到達するかを言います: 状態マシンは、ボタンではない任意の呼び出し元からの二回目の送信を依然として受け入れます。 その一文で削除された行は決定です; その一文なしで削除された行は、誰も二度と見つけることのないギャップです。
未実装のパスにはそう言うテストがある
この変更から意図的に除外されたパス、
このリリースでサポートされないロール、まだ解析されていないフォーマット、
NotImplementedExceptionをスローするかtodo!()を呼び出すブランチ、
それでも入力のクラスであり、
スイートは今日そこで何が起こるかを記録します。
そのパスが未実装として拒否されることをテストを書きなさい:
それが発生させる例外またはエラー値、
存在しないメニュー項目、「サポートされていません」と答えるリクエスト。
そのテストは今パスし、二つの仕事をします。 それはスイートのリストにギャップを置き、 そこでクラスをカウントする読者がそれを見つけ、 コメントやチケットの中ではなく。 そして、誰かがそのパスを実装した日にレッドになります、 それはその実際のケースが書かれなければならない瞬間です; 失敗するテストはリマインダーであり、それは置き換えられ、削除されません。
スキップマーカーまたはペンディングテストは、行が存在することをランナーに伝えますが、 それがカバーされているとは主張しません。 それは誤って緑として報告されることはありませんが、 その行は存在するクラスに対するテストの主張を持ちません。 それはスイートがペイロードを持つテストをペイロードを持たないテストと同じようにカウントすることを意味し、 スイートのサイズが網羅性と混同されることを可能にします。