TDDテストの粒度はどのくらいですか?


18

医療ソフトウェアのケースに基づいたTDDトレーニング中に、「ユーザーが[保存]ボタンを押すと、システムは患者を追加し、デバイスを追加し、デバイスデータレコードを追加する」というストーリーを実装します。

最終的な実装は次のようになります。

if (_importDialog.Show() == ImportDialogResult.SaveButtonIsPressed)
{
   AddPatient();
   AddDevice();
   AddDeviceDataRecords();
}

それを実装する方法は2つあります。

  1. それぞれが1つのメソッド(AddPatient、AddDevice、AddDeviceDataRecords)を検証した3つのテストが呼び出されました
  2. 3つのメソッドすべてを検証する1つのテストが呼び出されました

最初のケースでは、if句の条件に何か問題が発生した場合、3つのテストすべてが失敗します。しかし、テストが失敗した場合の2番目のケースでは、何が正確に間違っているのかわかりません。どのような方法を好むでしょうか。

回答:


8

しかし、テストが失敗した場合の2番目のケースでは、何が正確に間違っているのかわかりません。

これは、テストが生成するエラーメッセージの程度に大きく依存すると思います。一般に、メソッドが呼び出されたことを確認するにはさまざまな方法があります。たとえば、モックオブジェクトを使用すると、テスト中にどのメソッドが呼び出されなかったかを説明する正確なエラーメッセージが表示されます。呼び出しの影響を検知してメソッドが呼び出されたことを確認した場合、説明的なエラーメッセージを生成するのはユーザー次第です。

実際には、オプション1と2の選択は状況にも依存します。レガシープロジェクトで上記のコードを見る場合、条件が満たされたときに3つのメソッドのそれぞれが適切に呼び出されることを確認するために、ケース#2の実用的なアプローチを選択します。このコードを今開発している場合、3つのメソッド呼び出しは別々の時点で(おそらく互いに数日または数か月離れて)1つずつ追加される可能性が高いため、新しい別個の単体テストを追加します。各呼び出しを確認します。

また、どちらの方法でも、個々のメソッドのそれぞれが想定どおりに機能することを確認するために、個別のユニットテストを行う必要があります。


最終的にこれらの3つのテストを1つに結合するのが妥当だと思いませんか?
SiberianGuy

@Idsaは合理的な決定となりますが、実際にはこの種のリファクタリングはほとんど気にしません。繰り返しになりますが、私は優先順位が異なるレガシーコードで作業しています。既存のコードのテストカバレッジを拡大し、増加する単体テストの保守性を維持することに重点を置いています。
ペテルトレック

30

あなたの例の粒度は、単体テストと受け入れテストの違いのようです。

ユニットテストは、可能な限り少ない依存関係で、機能の単一ユニットをテストします。あなたの場合、4つのユニットテストがあります

  • AddPatientは患者を追加しますか(つまり、関連するデータベース関数を呼び出しますか)?
  • AddDeviceはデバイスを追加しますか?
  • AddDeviceDataRecordsはレコードを追加しますか?
  • サンプル呼び出しの修正されていないメイン関数は、AddPatient、AddDevice、AddDeviceFunctionsを呼び出します

ユニットテストは開発者向けであるため、コードが技術的に正しいという自信が得られます。

受け入れテストでは、ユーザーの観点から、組み合わせた機能をテストする必要があります。それらは、ユーザーストーリーに沿ってモデル化し、可能な限り高レベルにする必要があります。したがって、関数が呼び出されたかどうかを確認する必要はありませんが、ユーザーに見える利点が達成された場合

ユーザーがデータを入力したら、[OK]をクリックして...

  • ...患者リストに移動すると、指定された名前の新しい患者が表示されます
  • ...デバイスリストに移動すると、新しいデバイスが表示されます
  • ...新しいデバイスの詳細に移動すると、新しいデータレコードが表示されます

受け入れテストは、顧客向け、または顧客とのより良いコミュニケーションを構築するためのものです。

「あなたは何を好む」あなたの質問に答えるために:今、あなたにとってより大きな問題は何ですか、バグと回帰(=>より多くのユニットテスト)または全体像を理解し形式化(=>より受け入れテスト)


13

それを実装する方法は2つあります。

それは間違っています。

それぞれが1つのメソッド(AddPatient、AddDevice、AddDeviceDataRecords)を検証した3つのテストが呼び出されました

あなたは必要があり、それが動作を確認するためにこれを行います。

3つのメソッドすべてを検証する1つのテストが呼び出されました

あなたはしなければならない必ずAPIの作品であることを、これを行います。

クラスは、ユニットとして、完全にテストする必要があります。各メソッド。

3つの方法すべてをカバーするテストから始めることはできますが、あまりわかりません。

テストが失敗した場合、何が正確に間違っているのかはわかりません。

正しい。そのため、すべてのメソッドをテストます。

パブリックインターフェイスをテストする必要あります。このクラスは3つのプラス1つのことを行うため(ユーザーストーリーのために1つのメソッドにバンドルされている場合でも)、4つのすべてをテストする必要があります。3つの低レベルと1つのバンドル。


2

意味のある機能の文の単体テストを記述します。この文はメソッドに何度もマッピングされます(コードを適切に記述している場合)が、多くのメソッドが含まれて大きくなることもあります。

たとえば、システムに患者を追加するには、いくつかのサブルーチン(子関数)を呼び出す必要があると想像してください。

  1. VerifyPatientQualification
  2. EnsureDoctorExistence
  3. CheckInsuranceHistory
  4. EnsureEmptyBed

また、これらの各機能の単体テストを記述することもできます。


2

私が従ってきた簡単な経験則の1つは、テストに名前を付けることで、テストの実行内容を正確に説明することです。テストの名前が複雑すぎる場合は、テストが多すぎることを示している可能性があります。そのため、たとえば、オプション2で提案することを行うためにテストに名前を付けると、PatientIsAddedDeviceIsAddedAndDeviceDataRecordsWhenSavedのようになります。また、BDDから学べる教訓は、各テストが自然言語で記述できる単一の要件を実際に代表している場合、非常に興味深いと思います。

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