web-dev-qa-db-ja.com

テストのロジックを回避しながらコレクションを返すメソッドを単体テストする方法

データオブジェクトのコレクションを生成するメソッドをテスト駆動しています。オブジェクトのプロパティが正しく設定されていることを確認したいと思います。一部のプロパティは同じものに設定されます。その他は、コレクション内の位置に依存する値に設定されます。これを行う自然な方法は、ループを使用しているようです。ただし、Roy Osheroveは、単体テストでロジックを使用しないことを強くお勧めします(Art of Unit Testing、178)。彼は言う:

ロジックを含むテストでは、通常、一度に複数のテストが行​​われますが、テストの可読性が低く、壊れやすいため、これはお勧めできません。しかし、テストロジックは、隠れたバグを含む可能性のある複雑さも追加します。

テストは、原則として、try-catchでさえない制御フローのない一連のメソッド呼び出し、およびアサート呼び出しである必要があります。

ただし、デザインに問題はありません(データオブジェクトのリストを生成する方法はありますか。その値の一部は、シーケンスのどこに依存しているか?—正確に個別に生成してテストすることはできません)。デザインにテストに適さないものはありますか?それとも、オセロベの教えに厳格すぎるのでしょうか。それとも、この問題を回避することについて知らない秘密の単体テストの魔法がありますか? (私はC#/ VS2010/NUnitで書いていますが、可能であれば言語に依存しない答えを探しています。)

14
Kazark

TL; DR:

  • テストを書く
  • テストの実行量が多すぎると、コードの実行量も多すぎる可能性があります。
  • 単体テストではない可能性があります(ただし、悪いテストではありません)。

テストの最初の事柄は、教義が役に立たないことについてです。私は読書を楽しんでいます The Way of Testivus これはドグマに関するいくつかの問題を気軽に指摘しています。

記述する必要のあるテストを記述します。

テストを何らかの方法で記述する必要がある場合は、そのように記述します。テストをいくつかの理想的なテストレイアウトに強制しようとしたり、まったく持たせようとしても、良いことではありません。今日テストを行うことは、数日後に「完全な」テストを行うことよりも優れています。

醜いテストについても少し触れておきます。

コードが醜い場合、テストは醜いかもしれません。

醜いテストを書くのは好きではありませんが、醜いコードは最もテストする必要があります。

醜いコードでテストを書くのを止めさせないでください。しかし、醜いコードでテストを書くのを止めさせてください。

これらは、長い間フォローしてきた人にとっては自明の理と見なすことができます...そして、彼らはテストの考え方と書き方に染み付いています。その時点まで行ったことがない、または行おうとしている人にとっては、リマインダーが役立つことがあります(読み直しても、ドグマに縛られないようにするのに役立ちます)。


醜いテストを書くときに、コードがコードを実行しすぎていることを示している可能性があることを考慮してください。テストするコードが複雑すぎて、単純なテストを作成しても適切に実行できない場合は、コードをより小さな部分に分割して、より簡単なテストでテストできるようにすることを検討してください。すべてを行う単体テストを書くべきではありません(それはunitテストではない可能性があります)。 「神のオブジェクト」が悪いのと同じように、「神の単体テスト」も悪いので、前に戻ってコードをもう一度確認する必要があります。

あなたはすべきですこのような単純なテストを通じて、すべてのコードを妥当な範囲で実行できます。より大きな質問を扱うエンドツーエンドのテストを行うテスト(「私はこのオブジェクトをXMLにマーシャリングし、ルールを介してWebサービスに送信し、バックアウトし、マーシャリングを解除します」)は優れたテストですが、確かにそうではありません't aunitテスト(そして、統合テスト領域に分類されます-テストを実行するためにメモリデータベースでカスタムサービスを呼び出してカスタム化している場合でも)。それでもテストにXUnitフレームワークを使用する可能性がありますが、テストフレームワークはそれを単体テストにしません。

16
user40980

私の視点は、元の質問と回答を書いたときとは異なるため、新しい回答を追加しています。それらを1つにメッシュ化しても意味がありません。

私は元の質問で言った

ただし、デザインに問題はありません(データオブジェクトのリストを生成する方法はありますか。その値の一部は、シーケンスのどこに依存しているか?—正確に個別に生成してテストすることはできません)。

これは私が間違ったところです。昨年、関数型プログラミングを行った後、アキュムレータを使用したコレクション操作が必要なだけであることに気付きました。次に、ある機能を実行する純粋な関数として関数を記述し、いくつかの標準ライブラリ関数を使用してそれをコレクションに適用できます。

だから私の新しい答えは:関数型プログラミングテクニックを使用すれば、ほとんどの場合この問題を完全に回避できます。関数を記述して単一のものを操作し、それらを最後の瞬間にのみコレクションに適用できます。しかし、それらが純粋な場合は、コレクションを参照せずにテストできます。

より複雑なロジックについては、プロパティベースのテストを使用してください。ロジックがある場合は、テスト対象のコードのロジックよりも小さく、その逆である必要があります。各テストは、ケースベースの単体テストよりもはるかに多くのことを検証するため、少量のロジックに価値があります。

何よりも常にあなたのtypesに頼ってください。最強のタイプを入手して、それらを有利に使用してください。これにより、最初に作成する必要があるテストの数が減ります。

7
Kazark

一度に多くのことをテストしようとしないでください。コレクション内の各データオブジェクトの各プロパティは、1つのテストには多すぎます。代わりに、私はお勧めします:

  1. コレクションが固定長の場合、長さを検証するための単体テストを記述します。可変長の場合は、その動作を特徴付ける長さのテストをいくつか記述します(例:0、1、3、10)。どちらの方法でも、これらのテストでプロパティを検証しないでください。
  2. ユニットテストを記述して、各プロパティを検証します。コレクションが固定長で短い場合は、各テストの各要素の1つのプロパティに対してアサートするだけです。固定長で長い場合は、代表的な要素の小さいサンプルを選択して、それぞれ1つのプロパティに対してアサートします。可変長の場合、比較的短いが代表的なコレクション(つまり、3つの要素)を生成し、それぞれの1つのプロパティに対してアサートします。

この方法で行うと、テストが十分に小さくなり、ループを省略しても問題はありません。 C#/ Unitの例、テスト対象のメソッドICollection<Foo> generateFoos(uint numberOfFoos)

[Test]
void generate_zero_foos_returns_empty_list() { ... }
void generate_one_foo_returns_list_of_one_foo() { ... }
void generate_three_foos_returns_list_of_three_foos() { ... }
void generated_foos_have_sequential_ID()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("ID1", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID2", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID3", foos.Current.id);
}
void generated_foos_have_bar()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
}

「フラットユニットテスト」パラダイム(ネストされた構造/ロジックなし)に慣れている場合、これらのテストはかなりクリーンに見えます。したがって、ループが不足しているのではなく、あまりにも多くのプロパティを一度にテストしようとしているとして元の問題を特定することにより、テストでロジックが回避されます。

4
Kazark