web-dev-qa-db-ja.com

指数関数的なテストケースが必要なTDDおよび完全なテストカバレッジ

私は、クライアントからの非常に具体的な要件ごとに、検索結果の順不同リストをソートするのを支援するリストコンパレーターに取り組んでいます。要件では、重要度順に次のルールを使用して、ランク付けされた関連性アルゴリズムが必要です。

  1. 名前の完全一致
  2. 検索クエリのすべての単語の名前または結果の同義語
  3. 検索クエリのいくつかの単語の名前または結果の同義語(降順%)
  4. 説明内の検索クエリのすべての単語
  5. 説明内の検索クエリの一部の単語(降順%)
  6. 最終変更日が降順

このコンパレータの自然な設計の選択は、2の累乗に基づくスコア付きランキングのようでした。重要度の低いルールの合計は、重要度の高いルールの正の一致以上になることはありません。これは、次のスコアによって達成されます。

  1. 32
  2. 16
  3. 8 (Secondary tie-breaker score based on % descending)
  4. 4
  5. 2 (Secondary tie-breaker score based on % descending)
  6. 1

TDDの精神では、最初に単体テストから始めることにしました。固有のシナリオごとにテストケースを作成するのは、ルール3および5のセカンダリタイブレーカーロジックの追加のテストケースを考慮しない場合、最低63の固有のテストケースになります。

実際のテストは実際には少ないです。実際のルール自体に基づいて、特定のルールは下位のルールが常に真であることを保証します(たとえば、「すべての検索クエリの単語が説明に表示される」というルールは「一部の検索クエリの単語が説明に表示される」は常に真になります)。それでも、これらの各テストケースを書き出す作業のレベルは価値がありますか?これは、TDDで100%のテストカバレッジについて話すときに通常要求されるテストのレベルですか?そうでない場合、許容可能な代替テスト戦略は何でしょうか?

18
maple_shaft

あなたの質問は、TDDが「すべてのテストケースを最初に書く」ことと関係があることを暗示しています。 「TDDの精神にある」私見ではありません。実際にはagainstです。 TDDは「テスト駆動開発」の略であることを忘れないでください。したがって、必要なのは、実際に実装を「駆動」するテストケースだけで、それ以上は必要ありません。また、コードブロックの数が新しい要件ごとに指数関数的に増加するような方法で実装が設計されていない限り、テストケースの指数関数的な数も必要ありません。あなたの例では、TDDサイクルはおそらく次のようになります。

  • リストの最初の要件から始めます。「名前の完全一致」の単語は、他のすべてよりも高いスコアを取得する必要があります
  • これで、最初のテストケース(たとえば、特定のクエリに一致するWord)を記述し、そのテストに合格する最小限の作業コードを実装します
  • 最初の要件に2つ目のテストケースを追加します(例:クエリに一致しない単語)。新しいテストケースを追加する前に、既存の2番目のテストに合格するまでのコード
  • 実装の詳細に応じて、テストケースを追加してもかまいません。たとえば、空のクエリ、空のWordなど(TDDはホワイトボックスアプローチでは、テストケースを設計するときに実装を知っているという事実を利用できます)。

次に、2番目の要件から始めます。

  • 「検索クエリのすべての単語の名前または結果の同義語」は、「名前の完全一致」より低いスコアを取得する必要がありますが、他のすべてより高いスコアを取得する必要があります。
  • 上記と同様に、この新しい要件のテストケースを次々に作成し、新しいテストごとにコードの次の部分を実装します。コードとテストケースの中間でリファクタリングすることを忘れないでください。

ここにキャッチがあります:要件/カテゴリ番号「n」のテストケースを追加する場合、カテゴリ「n-1」のスコアがより高いことを確認するためのテストを追加するだけで済みます。カテゴリ「n」のスコア。以前に作成したテストでは、そのカテゴリのスコアが正しい順序であることが確認されるため、カテゴリ1、...、n-1の他のすべての組み合わせに対してテストケースを追加する必要はありません。

したがって、これにより、指数関数的にではなく、要件の数にほぼ比例して増加するいくつかのテストケースが得られます。

17
Doc Brown

事前定義された条件リストを通過し、チェックが成功するたびに現在のスコアに2を掛けるクラスを作成することを検討してください。

これは、いくつかの模擬テストを使用して、非常に簡単にテストできます。

次に、条件ごとにクラスを記述できます。各ケースのテストは2つだけです。

私はあなたのユースケースを本当に理解していませんが、うまくいけばこの例が役立つでしょう。

public class ScoreBuilder
{
    private ISingleScorableCondition[] _conditions;
    public ScoreBuilder (ISingleScorableCondition[] conditions)
    {
        _conditions = conditions;
    }

    public int GetScore(string toBeScored)
    {
        foreach (var condition in _conditions)
        {
            if (_conditions.Test(toBeScored))
            {
                // score this somehow
            }
        }
    }
}

public class ExactMatchOnNameCondition : ISingleScorableCondition
{
    private IDataSource _dataSource;
    public ExactMatchOnNameCondition(IDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public bool Test(string toBeTested)
    {
        return _dataSource.Contains(toBeTested);
    }
}

// etc

2 ^条件テストがすぐに4+(2 *条件)になることがわかります。 20は64に比べて圧倒的ではありません。さらに別のクラスを後で追加する場合、既存のクラス(開閉式の原則)を変更する必要がないため、64の新しいテストを作成する必要はありません。 2つの新しいテストを含む別のクラスを追加し、それをScoreBuilderクラスに挿入します。

13
pdr

それでも、これらの各テストケースを書き出す作業のレベルは価値がありますか?

「それに値する」を定義する必要があります。この種のシナリオの問題は、テストの有用性が低下することです。確かに、あなたが書いた最初のテストは、それだけの価値があります。優先順位の明らかなエラーや、単語を分割しようとするときの解析エラーなどを見つけることができます。

2番目のテストは、コード内の別のパスをカバーし、おそらく別の優先関係をチェックするため、価値があります。

639.99のテストは99.99%の自信があるため、コードのロジックまたは別のテストでカバーされているため、おそらく63番目のテストは価値がありません。

これは、TDDで100%のテストカバレッジについて話すときに通常要求されるテストのレベルですか?

100%カバレッジとは、すべてのコードパスが実行されるということです。これは、ルールのすべての組み合わせを実行することを意味するわけではありませんが、コードが下がる可能性のあるすべての異なるパス(指摘するように、一部の組み合わせはコードに存在できない場合があります)。しかし、TDDを実行しているため、パスをチェックするための「コード」はまだありません。プロセスの書簡では、すべて63歳以上としています。

個人的には、カバレッジ100%は夢です。それを超えて、それは実用的ではありません。単体テストはあなたに役立つために存在し、その逆ではありません。より多くのテストを行うと、利益(テストがバグを防ぐ可能性+コードが正しいという信頼)に対する利益が減少します。コードが何をするかに応じて、そのスライディングスケールのどこでテストを停止するかを定義します。コードが原子炉を実行している場合、おそらく63以上のすべてのテストに価値があります。コードが音楽アーカイブを整理している場合は、おそらくそれよりもはるかに少なくて済むでしょう。

4
Telastyn

これはTDDの完全ケースであると私は主張します。

テストする基準のセットがわかっており、それらのケースを論理的に分類します。あなたがそれらを今または後でユニットテストするつもりであると仮定すると、既知の結果を取り、それを中心に構築することは理にかなっているようです。

さらに、新しい検索ルールを追加すると、既存のルールに違反するかどうかを確認できます。コーディングの最後にこれらすべてを行うと、おそらく、1つを修正して1つを修正しなければならないという大きなリスクが発生します。または微調整が必​​要です。

4
Wonko the Sane

100%のテストカバレッジを、すべての単一のメソッドに対する仕様を記述したり、コードのすべての順列をテストしたりするものとして厳密に解釈するのは好きではありません。これを狂信的に行うと、ビジネスロジックを適切にカプセル化せず、サポートされるビジネスロジックを記述するという意味では一般的に無意味なテスト/仕様を生み出す、クラスのテスト駆動型設計につながる傾向があります。代わりに、ビジネスルール自体と同じようにテストを構造化することに焦点を当て、テストは一般的なユースケースと同じように実際に説明されているため、テスターがテストを簡単に理解できることを明示的に期待して、コードのすべての条件付きブランチをテストするように努めています実装されたビジネスルール。

このアイデアを念頭に置いて、リストした6つのランキング要素を個別に徹底的に単体テストし、続いて2つまたは3つの統合スタイルテストを実行して、期待される全体的なランキング値に結果をロールアップします。たとえば、ケース#1、名前の完全一致の場合、少なくとも2つの単体テストを実行して、正確な場合とそうでない場合、および2つのシナリオが期待スコアを返すことをテストします。大文字と小文字が区別される場合は、「完全一致」と「完全一致」をテストするケースと、句読点、余分なスペースなどの他の入力バリエーションも、予想されるスコアを返します。

ランキングスコアに影響するすべての個別の要素を調べたら、基本的にこれらは統合レベルで正しく機能していると想定し、それらを組み合わせた要素が最終的な予測ランキングスコアに正しく貢献するように焦点を合わせます。

ケース#2 /#3と#4 /#5が同じ基本メソッドに一般化されているが、異なるフィールドを渡すと仮定すると、基本メソッドのユニットテストの1つのセットを記述し、特定のテストを行うための簡単な追加ユニットテストを記述するだけで済みます。フィールド(タイトル、名前、説明など)および指定したファクタリングでのスコアリング。これにより、テスト作業全体の冗長性がさらに削減されます。

このアプローチでは、上記のアプローチはおそらくケース#1で3または4のユニットテストを生成します。おそらく、類義語を含む一部/すべての10のスペックに加えて、ケース#2-#5および2の正しいスコア付けで4つのスペックが得られます。最終日付順ランキングの3つの仕様に、次に、可能性のある方法で組み合わされた6つのケースすべてを測定する3つから4つの統合レベルのテスト(確認するために実行する必要があるコードに問題が明確に見られない限り、現時点ではあいまいなEdgeケースは忘れてください)その状態は処理されます)またはしないでください後のリビジョンで違反/破損することを確認します。これにより、作成されたコードの100%を実行するための約25程度の仕様になります(作成されたメソッドの100%を直接呼び出さなかったとしても)。

1
Michael Lang

私は100%テストカバレッジのファンではありません。私の経験では、1つまたは2つのテストケースでテストできるほど単純なものであれば、失敗することはほとんどありません。それが失敗した場合、通常はとにかくテストの変更を必要とするアーキテクチャの変更が原因です。

そうは言っても、あなたのような要件については、誰も私を作っていない個人的なプロジェクトであっても、常にユニットテストを徹底的に行っています。何かをテストするために必要な単体テストが多いほど、単体テストで節約できる時間が長くなります。

一度に頭にできるものはたくさんあるからです。 63の異なる組み合わせで機能するコードを記述しようとしている場合、別の組み合わせを壊さずに1つの組み合わせを修正することはしばしば困難です。手動で他の組み合わせを何度もテストすることになります。手動テストははるかに遅いため、変更を加えるたびにすべての可能な組み合わせを再実行する必要はありません。これにより、何かを見逃す可能性が高くなり、すべてのケースで機能しないパスを追跡する時間を無駄にする可能性が高くなります。

手動テストと比較して節約される時間を除けば、精神的な負担がはるかに少ないため、誤って回帰を導入することを心配することなく、目前の問題に簡単に集中できます。これにより、バーンアウトすることなく、より速く、より長く作業できます。私の意見では、メンタルヘルスのメリットだけでも、たとえ時間を節約できなかったとしても、複雑なコードを単体テストするコストに見合う価値があります。

1
Karl Bielefeldt