web-dev-qa-db-ja.com

テストが単一のアサーションを実行するか、複数の関連するアサーションが受け入れ可能か

クライアントがAPIエンドポイントにリクエストを作成し、リクエストが成功したかどうかに応じて構造とデータが変化するJSONレスポンスを返すと想定します。可能な応答は次のようになります。

成功応答のあるシナリオ

{
  "status": XXX,
  "data": [{
    ...
  }]
}

失敗応答のあるシナリオ

{
  "status": XXX,
  "errors": [{
    ...
  }]
}

上記のテストシナリオの例は次のとおりです。

  • 予期されるステータスコードをアサートする
  • 予想されるJSON構造をアサートする
  • 予期されるJSONデータをアサートする

実行できるアサーションが複数あるテストを実行する場合、アサーションごとに単一のテストを提供するか、アサーションを単一のテストにグループ化することをお勧めしますか?

6
Unflux

ユニットテストが何に適しているかを自問してください。主な目的は、コードを変更した後、すべての単体テストを実行し、すべてが成功した場合は、コードに問題がないことを確信することです。この目的のために重要なのは、合格した独立したアサーションの数です。したがって、この目的のために、100のアサーションを使用した単一のユニットテストは問題ありません。

2番目の目的は、ユニットテストが失敗した場合、何が問題かを可能な限り正確に把握し、正確な失敗を探す時間を節約することです。ユニットテストごとに1つのアサートを行うことで、正確な障害を特定する時間を節約できます。

環境に応じて、これは多かれ少なかれ役立ちます。したがって、特定の環境でこれがどれほど重要であるかは、判断の呼びかけです。単体テストの最初のアサーションのみを報告する環境もあれば、すべてを報告する環境もあります。それは明らかに違いを生みます。

しかし、最も重要なのはテストカバレッジです。ユニットテストごとに1つのアサーションを作成する優れたユニットテスターであるが、50回のテストの後、退屈してテストの作成を停止した場合、20回のアサーションでユニットテストを作成し、20回のユニットテストの後も引き続き続行します。より良いことをしています。

PS。一部の人々は、「テストごとに1つのアサート」というルールを採用し、それを使用して愚かなことを正当化します。私のコードが5つの値を生成し、ユニットテストが5つの結果がどうあるべきかを知っているとしましょう。今、あなたはルールに反対することができます:

Assert(result1 == expected1)
  ...
Assert(result5 == expected5)

または、「ルールに従う」と書いてください

Assert(result1 == expected1 && ... && result5 == expected5)

そして、何を推測します:2番目は、実際の問題が何であるかを知る手がかりを私に残しません。最初の問題は最悪の場合でも最初の問題を教えてくれます。

8
gnasher729

各単体テストは、1つの要件を表明する必要があります。

現在、すべてのメソッドに必ずしも正式な要件があるとは限りません。メソッドは、このような正式な要件を満たすことを目的として書かれています。ただし、メソッドは1つのことだけを行う必要があり、単一の目的を持つ必要があるので、動作の観点からメソッドに何を期待するかについての公正な考えがすでにあるはずです。

最初の例を考えてみましょう。出力全体に対して常にアサートすることができます(疑似コードが続きます)。

Assert.AreEqual(result, @"{
  'status': XXX,
  'data': [{
    ...
  }]
}");

しかし、そのようなテストは脆弱であり、テストが失敗した場合、何が問題だったか、どの要件が満たされていなかったかを示すものはありません。出力を調べて実際の問題の場所を確認し、問題を分析して、満たされていない要件を特定する必要があります。

単一の要件を表明することは、テストする要件を具体的に示す意味のある名前をユニットテストに付けることができることを意味します。 それを行うためのいくつかの方法 があります。

Given_UserIsAuthenticated_When_InvalidAccountNumberIsUsedToWithdrawMoney_Then_TransactionWillFail()  

テストが失敗すると、意味のある名前でテストランナーに表示され、満たされていない要件がすぐにわかります。

4
Robert Harvey

開発/設計ループの一部としてテストを実行している場合は、テストの失敗をどのように説明するかはそれほど重要ではないことに注意してください。 knowの場合、テストによって出力される多くの情報は必要ありません。根本的な原因は、変更したコード行です。

問題となる傾向があるのは、一連の新しいコードがブロックとしてマージされるときであり、導入されたばかりの問題を追跡するために詳細な信号が必要です。

実行できるアサーションが複数あるテストを実行する場合、アサーションごとに単一のテストを提供するか、アサーションを単一のテストにグループ化することをお勧めしますか?

それでは、最初に、Arrange-Act-Assertレシピに従うテストについて具体的に話していると仮定しましょう。したがって、システムを作成し、そのシステムにゼロ個以上のメッセージを送信し、最後にシステムが何らかの制約を満たしているかどうかを測定します。

その制約は通常、「システムからいくつかの値を抽出し、それをいくつかの期待値と比較する」という形をしています。

このような比較には、N個の異なるチェックが適用される場合があります。

1つのオプションは、チェックごとに異なるテストを行うことです。テストでは、配置フェーズと動作フェーズが複製されますが、アサートフェーズは少し異なります。良いニュースは、すべてのチェックが実行され、互いに干渉しないことです。テストが適切に分離されている場合は、テストを同時に実行できます。悪いニュースは、追加の構造がなければ、テストハーネスはテストを一緒にバンドルする必要があることを必ずしも理解せず、何が何であるかを完全に理解するにはseveral場所を調べる必要があるということです物事が失敗したときに続く。

別のオプションは、すべてのチェックを1つのテストに入れることです。良いニュースは、特定のシナリオが1か所にあるため、コードは、どこにでも散在させるのではなく、1か所で満たそうとしている仕様を記述していることです。悪いニュースは、単一の失敗したチェックがアサートをトリップすることです。その後にスケジュールされたチェックは決して評価されず、何が行われているのかを完全に把握できません。

3番目のオプションは、各チェックを述語として処理し、結果をANDで処理し、結果をアサートすることです。これにより、すべてのロジックと評価されるすべてのチェックを使用した単一のテストが得られますが、どのチェックが問題であったかについての弱いシグナルしか得られません。

4番目のオプションも同様です。チェックの結果をまとめて収集しますが、単純なブール述語ではなく結果をアサートします。期待される結果の順序付きリストは次のとおりです。実際の結果の順序付きリストはここにあり、一致しない場合はアサートします。これにより、多くのすべてが得られます。すべてのチェックが1か所にあり、すべてのチェックが実行され、すべての失敗に関する明確なメッセージが表示されます。

悪い知らせは、この4番目のアプローチはSUnitの設計方法ではなく、したがってJUnitの設計方法ではなく、したがってこれらのツールのすべての子孫の設計方法ではないということです。

関数型プログラマーは、この最後のアプローチの方が快適かもしれません。チェックのリストを作成し、マップしてチェック結果のリストを作成してから、それらのチェック結果を折りたたみます。

これらのアプローチのいずれも、テストが配置または動作フェーズで前提条件をたまたまトリップした場合、特にうまく機能しないことに注意してください。

ベストプラクティス?あなたの選択

1)あなたが知っていることを文書化し、あなたが推測したことを文書化し、あなたの決定を文書化し、あなたが期待する結果を文書化し、その文書を時々見直し、通話を再評価してください。

2)実験し、証拠を蓄積し、特定のコンテキストでどの外部要因が意味があるかを推測する能力を向上させます。

1
VoiceOfUnreason