web-dev-qa-db-ja.com

単体テストで期待される結果をハードコーディングする必要がありますか?

単体テストの期待される結果をハードコードする必要がありますか、それとも初期化された変数に依存する可能性がありますか?ハードコードまたは計算された結果により、単体テストでエラーが発生するリスクが高まりますか?考慮していない他の要因はありますか?

たとえば、これらの2つのどちらがより信頼できる形式ですか?

[TestMethod]
public void GetPath_Hardcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\\Output Folder\\fields\\that later\\determine\\a folder";
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

[TestMethod]
public void GetPath_Softcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\\Output Folder\\" + string.Join("\\", target.Field1, target.Field2, target.Field3, target.Field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

編集1: DXMの回答に対して、オプション3は推奨されるソリューションですか?

[TestMethod]
public void GetPath_Option3()
{
    string field1 = "fields";
    string field2 = "that later";
    string field3 = "determine";
    string field4 = "a folder";
    MyClass target = new MyClass(field1, field2, field3, field4);
    string expected = "C:\\Output Folder\\" + string.Join("\\", field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}
30
Hand-E-Food

計算された期待値は、より堅牢で柔軟なテストケースにつながると思います。また、期待される結果を計算する式で適切な変数名を使用することにより、そもそも期待される結果がどこから来たかがより明確になります。

そうは言っても、具体的な例では、SUT(テスト対象システム)を計算の入力として使用するため、「ソフトコード化」メソッドは信頼できません。 MyClassにフィールドが正しく格納されていないバグがある場合、期待値の計算ではtarget.GetPath()のように誤った文字列が使用されるため、テストは実際に成功します。

私の提案は、意味のある場所で期待値を計算することですが、計算がSUT自体のコードに依存しないことを確認してください。

私の応答へのOPの更新への応答:

はい、私の知識に基づいていますが、TDDを行う上である程度の経験があるため、オプション#3を選択します。

28
DXM

コードが次のような場合:

MyTarget() // constructor
{
   Field1 = Field2 = Field3 = Field4 = "";
}

2番目の例はバグをキャッチしませんが、最初の例はキャッチします。

一般的に、バグを隠す可能性があるため、ソフトコーディングはお勧めしません。例えば:

string expected = "C:\\Output Folder" + string.Join("\\", target.Field1, target.Field2, target.Field3, target.Field4);

問題を見つけられますか?ハードコードされたバージョンでは、同じミスを犯すことはありません。ハードコードされた値よりも計算を正しく行うことは困難です。そのため、ソフトコード化された値よりもハードコード化された値を使用することを好みます。

しかし、例外があります。コードをWindowsおよびLinuxで実行する必要がある場合はどうなりますか?パスが異なるだけでなく、異なるパス区切り文字を使用する必要があります!両者の違いを抽象化する関数を使用してパスを計算することは、そのコンテキストでは意味があるかもしれません。

16
Winston Ewert

私の意見では、あなたの提案はどちらも理想的とは言えません。それを行う理想的な方法はこれです:

[TestMethod]
public void GetPath_Hardcoded()
{
    const string f1 = "fields"; const string f2 = "that later"; 
    const string f3 = "determine"; const string f4 = "a folder";

    MyClass target = new MyClass( f1, f2, f3, f4 );
    string expected = "C:\\Output Folder\\" + string.Join("\\", f1, f2, f3, f4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

つまり、テストはオブジェクトの入力と出力にのみ基づいて機能し、オブジェクトの内部状態に基づいて機能するべきではありません。オブジェクトはブラックボックスとして扱う必要があります。 (これは単なる例であるため、Path.Combineの代わりにstring.Joinを使用することの不適切さなど、他の問題は無視します。)

4
Mike Nakis

議論には2つの側面があります。

1。テストケースにターゲット自体を使用する
最初の質問は、テストスタブで作業の一部を信頼して実行するためにクラス自体を使用すべき/できますか? -答えは[〜#〜] no [〜#〜]です。これは、一般に、テストしているコードについて決して想定してはならないためです。これが適切に行われない場合、バグは一部のユニットテストの影響を受けなくなります。

2。ハードコーディング
あなたはハードコードをすべきですか?ここでも答えはNoです。他のソフトウェアと同じように、物事が進化すると、情報のハードコーディングが難しくなります。たとえば、上記のパスを再度変更する場合は、追加のユニットを書き込むか、変更を続ける必要があります。より良い方法は、簡単に適応できる個別の構成から派生した入力と評価の日付を保持することです。

たとえば、ここでは、テストスタブを修正する方法を示します。

[TestMethod]
public void GetPath_Tested(int CaseId)
{
    testParams = GetTestConfig(caseID,"testConfig.txt"); // some wrapper that does read line and chops the field. 
    MyClass target = new MyClass(testParams.field1, testParams.field2);
    string expected = testParams.field5;
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}
2
Dipan Mehta

可能なコンセプトはたくさんあり、違いを確認するためにいくつかの例を作成しました

[TestMethod]
public void GetPath_Softcoded()
{
    //Hardcoded since you want to see what you expect is most simple and clear
    string expected = "C:\\Output Folder\\fields\\that later\\determine\\a folder";

    //If this test should also use a mocked filesystem it might be that you want to use
    //some base directory, which you could set in the setUp of your test class
    //that is usefull if you you need to run the same test on different environments
    string expected = this.outputPath + "fields\\that later\\determine\\a folder";


    //another readable way could be interesting if you have difficult variables needed to test
    string fields = "fields";
    string thatLater = "that later";
    string determine = "determine";
    string aFolder = "a folder";
    string expected = this.outputPath + fields + "\\" + thatLater + "\\" + determine + "\\" + aFolder;
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    //in general testing with real words is not needed, so code could be shorter on that
    //for testing difficult folder names you write a separate test anyway
    string f1 = "f1";
    string f2 = "f2";
    string f3 = "f3";
    string f4 = "f4";
    string expected = this.outputPath + f1 + "\\" + f2 + "\\" + f3 + "\\" + f4;
    MyClass target = new MyClass(f1, f2, f3, f4);

    //so here we start to see a structure, it looks more like an array of fields
    //so what would make testing more interesting with lots of variables is the use of a data provider
    //the data provider will re-use your test with many different kinds of inputs. That will reduce the amount of duplication of code for testing
    //http://msdn.Microsoft.com/en-us/library/ms182527.aspx


    The part where you compare already seems correct
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

要約すると、一般的に、最初にハードコードした最初のテストは、単純で、要点に直結しているため、私にとって最も理にかなっています。パスを何度もハードコーディングし始めた場合は、セットアップメソッドに入れてください。

今後の構造化テストについては、データソースをチェックアウトして、テスト状況がさらに必要な場合にデータ行を追加できるようにします。

0
Luc Franken

最新のテストフレームワークでは、メソッドにパラメーターを提供できます。私はそれらを活用します:

[TestCase("fields", "that later", "determine", "a folder", @"C:\Output Folder\fields\that later\determine\a folder")]
public void GetPathShouldReturnFullDirectoryPathBasedOnItsFields(
    string field1, string field2, string field3, string field,
    string expected)
{
    MyClass target = new MyClass(field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

私の見解では、これにはいくつかの利点があります。

  1. 開発者は、SUTからユニットテストにコードの見かけ上単純な部分をコピーしたくなることがよくあります。 Winstonが指摘しているように 、それらにはまだトリッキーなバグが隠されている可能性があります。予想される結果を「ハードコーディング」すると、元のコードが正しくないのと同じ理由でテストコードが正しくない状況を回避するのに役立ちます。しかし、要件の変更により、何十ものテストメソッド内に埋め込まれたハードコードされた文字列を追跡する必要がある場合は、煩わしい場合があります。すべてのハードコードされた値を1つの場所(テストロジックの外)に置くことで、両方の長所を利用できます。
  2. 1行のコードで、さまざまな入力と予想される出力のテストを追加できます。これにより、テストコードDRYを維持し、維持しやすくしながら、より多くのテストを作成することができます。テストを追加するのは非常に安価であるため、新しいテストケースに心が開かれます。まったく新しいメソッドを作成する必要があるかどうか考えたことはありません。たとえば、入力の1つにドットが含まれている場合、どのような動作を期待できますか?バックスラッシュ?空の場合はどうなりますか?または空白スペース?または開始または空白で終了しましたか?
  3. テストフレームワークは、各TestCaseを独自のテストとして扱い、提供された入力と出力をテスト名に含めます。すべてのTestCaseが合格した場合、1つが合格すれば、どれが故障し、どのように他のすべてと異なるかを確認するのは非常に簡単です。
0