要件または方法によるユニットテストの分割


16

最初に、タイトルをおaびします。それを説明する最も簡単な方法は考えられませんでした!

ユニットテストを書きたいメソッドがあります。メソッドの実装については説明せず、テストのみを行いたいので、かなり汎用的にしておきます。メソッドは次のとおりです。

public void HandleItem(item a)
{         
     CreateNewItem();
     UpdateStatusOnPreviousItem();
     SetNextRunDate();
}

そのため、このクラスにはパブリックメソッドが1つあり、このメソッドがプライベートメソッドを呼び出してロジックを実行します。

それで、ユニットテストを書くとき、私は3つすべてが行われたことをチェックしたいです。それらはすべて同じ実行で呼び出されるため、1つのテストとして実行できると考えました。

public void GivenItem_WhenRun_Thenxxxxx
{
     HandleItem(item);
     // Assert item has been created
     // Assert status has been set on the previous item
     // Assert run date has been set
}

しかし、3つの別々のテストとして書くこともできると考えました。

public void GivenItem_WhenRun_ThenItemIsCreated()
{
    HandleItem(item);
}

public void GivenItem_WhenRun_ThenStatusIsUpdatedOnPreviousItem()
{
   HandleItem(item);
}

public void GivenItem_WhenRun_ThenRunDateIsSet()
{
     HandleItem(item);
}

したがって、本質的に要件をリストしているのでこれは良いように見えますが、3つすべてが関連しており、テストされたメソッドで実行されるのとまったく同じ作業が必要なので、同じコードを3回実行しています。

これで推奨されるアプローチはありますか?

ありがとう

回答:


29

両方のアプローチには微妙な違いがあります。最初のケースでは、最初のケースAssertが失敗すると、他の2つは実行されなくなります。2番目のケースでは、1つが失敗した場合でも、3つすべてのテストが常に実行されます。テストされた機能の性質に応じて、これはあなたのケースに合うかもしれませんし、合わないかもしれません:

  • 3つのアサートを別のアサートから独立して実行するのが理にかなっている場合、1つが失敗しても他の2つはまだ失敗しない可能性があるため、2番目のアプローチには、1回の実行で3つのテストすべての完全なテスト結果が得られるという利点があります これは、次のビルドを実行する前に一度に最大3つのエラーを修正する機会を与えるため、ビルド時間が注目に値する場合に役立ちます。

  • ただし、最初のテストの失敗が常に他の2つのテストも失敗することを意味する場合は、おそらく最初のアプローチを使用することをお勧めします(事前にテストを実行することが既にわかっている場合、テストを実行することはあまり意味がないので)不合格)。


2
+1、良い点。ビルド時間がボトルネックになることもありませんでした。
キリアンフォス

1
@KilianFoth:C ++で十分な頻度で作業していない:(
Matthieu M.

1
@MatthieuM:公平であるために、質問が「C#の」タグ付けされている
ドク・ブラウン

10

簡単な答え:テストがすべての機能をカバーすることは、テストがそれを行う方法よりもはるかに重要です

より長い答え:これらのほぼ同等のソリューションの中から選択したい場合は、最適なものに補助的な基準を使用できます。例えば、

  • 可読性:メソッドが密接に関連していない多くのことを行う場合、結合テストを理解するのは難しいかもしれません。ただし、メソッド自体も理解しにくい場合があるため、テストではなくメソッドをリファクタリングする必要があるかもしれません!)
  • 効率:メソッドの実行に時間がかかる場合、3つのチェックすべてを組み合わせて時間を節約する弱い理由になる可能性があります
  • 効率性2:フレームワークのセットアップコードの実行に時間がかかる場合、複数のテストメソッドを回避するための弱い理由にもなります。(ただし、これが実際に問題である場合は、テストセットアップを修正または変更する必要があります。回帰テストは、高速で実行できない場合、その価値の多くを失います。)

2

複数のアサートで1つのメソッド呼び出しを使用します。その理由は次のとおりです。

HandleItem(a)をテストするとき、メソッドがアイテムを正しい状態にしたことをテストしています。「テストごとに1つのアサート」ではなく、「テストごとに1つの論理概念」を考えてください。

質問:CreateNewItemが失敗したが、他の2つのメソッドが成功した場合、これはHandleItemが正常に完了したことを意味しますか?私は推測していない。

複数のアサート(適切なメッセージ)により、何が失敗したかを正確に知ることができます。通常、複数のアサートを回避するためではなく、複数の入力または入力状態に対してメソッドを複数回テストします。

IMO、これらの質問は通常、何か他のものの兆候です。これは、HandleItemが他のメソッドに委任しているように見えるため、実際に「ユニットテスト」できるものではないことを示しています。HandleItemが他のメソッドを正しく呼び出すことを単純に検証している場合、それはより統合テストの候補になります(この場合、まだ3つのアサートがあります)。

他の3つのメソッドを公開し、それらを個別にテストすることを検討してください。または、別のクラスに抽出することもできます。


0

2番目のアプローチを使用します。最初のアプローチでは、テストが失敗した場合、すぐに理由がわかりません。失敗した3つの機能の1つである可能性があるためです。2番目のアプローチでは、問題が発生した場所がすぐにわかります。テストセットアップ関数内に重複したコードを配置できます。


-1

私見では、このメソッドの3つの部分を別々にテストして、コードの同じ部分を2回調べることを避けながら、どこで問題が起こっているかをより具体的に知る必要があります。


-2

ユースケースごとに個別のテストメソッドを作成する強力なケースはないと思います。3つの変数条件すべてから結果を取得したい場合は、3つすべてをテストし、それらを連結しstringてテストが完了したら文字列がまだ空であるかどうかをアサートすることで失敗を出力できます。それらをすべて同じメソッドに保つことにより、条件と失敗メッセージは、後で分離される可能性のある3つのメソッドに分割するのではなく、1つの場所でメソッドの予想される事後条件を文書化します。

これは、1つのテストでより多くのコードが使用されることを意味しますが、非常に多くの事後条件が問題になる場合は、テストメソッドをリファクタリングして、内部の個々のメソッドをテストしHandleItemます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.