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


14

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

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

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

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


4
ループしないことをお勧めします。3番目のもののバーがFrobに設定されていることをテストする場合は、3番目のもののバーがFrobであることを具体的にチェックするテストを作成します。それはそれ自体での1つのテストです。まっすぐに進んでください。ループはありません。5つのもののコレクションを取得するというテストであれば、それも1つのテストです。これは、明示的またはその他の方法でループを一度も実行したことがないということではなく、頻繁に実行する必要がないということです。また、Osheroveの本を実際のルールよりも多くのガイドラインとして扱ってください。
アンソニーペグラム

1
@AnthonyPegramセットは順不同です-Frobは3番目、2番目になることがあります。inテストが「Frobが既存のコレクションに正常に追加された」場合、これに頼ることはできず、ループ(またはPythonのような言語機能)が必要になります。
イズカタ

1
@Izbata、彼の質問は、特に順序が重要だと述べています。彼の言葉:「他の人は、コレクション内の自分の位置に依存する値に設定されます。」C#(彼が参照する言語)には、挿入順になっているコレクションタイプがたくさんあります。さらに、言及した言語であるPythonのリストの順序に依存することもできます。
アンソニーペグラム

また、コレクションでResetメソッドをテストしているとしましょう。コレクションをループして、各アイテムをチェックする必要があります。コレクションのサイズにもよりますが、ループでテストしないのはばかげています。または、コレクション内の各アイテムをインクリメントすることになっているものをテストしているとしましょう。すべての項目を同じ値に設定し、増分を呼び出してから確認できます。そのテストは最悪です。それらのいくつかを異なる値に設定し、インクリメントを呼び出し、すべての異なる値が正しくインクリメントされることを確認する必要があります。コレクション内のランダムなアイテムを1つだけチェックすると、チャンスがたくさんあります。
iheanyi 14

私はこの方法で答えるつもりはありません。なぜなら、私は何億もの下票を得るからです。しかし、私はしばしばただtoString()コレクションであり、それがどうあるべきかと比較します。シンプルで動作します。
user949300

回答:


16

TL; DR:

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

テストの最初のことは、ドグマが役に立たないことです。「教義の道」を読んで、ドグマに関するいくつかの問題を軽快に指摘しています。

作成する必要があるテストを作成します。

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

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

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

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

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

これらは、長い間フォローしてきた人にとっては自明であると考えることができます...そして、彼らはテストの考え方や書き方に染み込んでしまいます。その時点まで到達しておらず、その時点に到達しようとしている人々にとっては、リマインダーが役立つ場合があります(読み直すことで、ドグマに縛られるのを防ぐことができます)。


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

このような簡単なテストにより、すべてのコードを妥当な範囲で実行できるはずです。大きな質問(「このオブジェクトをXMLにマーシャリングし、ルールを介してWebサービスに送信し、ルールを介してバックアウトし、マーシャリングしない」)を扱うエンドツーエンドのテストを行うテストは優れたテストですが、確かにそうではありません「T ユニットテストを(と統合テストの分野に該当する-それはテストを行うためにメモリデータベースで、それが呼び出すサービスやカスタムを嘲笑している場合でも)。テストにはまだXUnitフレームワークを使用する可能性がありますが、テストフレームワークは単体テストになりません。


7

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

私は元の質問で言った

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

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

したがって、私の新しい答えは、関数型プログラミング手法を使用すれば、ほとんどの場合、この問題を完全に回避できます。単一のものを操作する関数を作成し、最後の時点でそれらをコレクションにのみ適用できます。しかし、それらが純粋であれば、コレクションを参照せずにテストできます。

より複雑なロジックの場合は、プロパティベースのテストに頼ってください。ロジックがある場合、テスト対象のコードのロジックより小さく、逆でなければなりません。各テストは、ケースベースのユニットテストよりもはるかに多くのロジックを検証するため、少量のロジックに値します。

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


4

一度に多くのことをテストしようとしないでください。コレクション内の各データオブジェクトの各プロパティは、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);
}

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


1
Osheroveは、3つのアサーションを持っているため、大皿に頭を抱えています。;)最初の失敗は、残りを検証しないことを意味します。また、ループを実際に回避しなかったことにも注意してください。実行されたフォームに明示的に展開しました。厳しい批判ではありませんが、テストケースを可能な限り最小限に分離し、何かが失敗したときにより具体的なフィードバックを提供し、まだ成功する可能性がある(または失敗する可能性がある)他のケースを検証し続けるためのさらなる提案を行うだけです独自の特定のフィードバック)。
アンソニーペグラム

3
@AnthonyPegram私はテストごとに1つのアサートのパラダイムについて知っています。私は「テスト1つ」というマントラを好んでいます(クリーンコードでは、テストごとに1つのアサートに対して、ボブマーチンが提唱しています)。サイドノート:「期待」対「アサート」のある単体テストフレームワークは素晴らしい(Googleテスト)。残りについては、例を挙げて提案を完全な回答に分けてみませんか?私は恩恵を受けることができると思います。
カザーク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.