C# – DynamicData 属性を使用して関数とオブジェクトをパラメーター化されたテストに渡す

パラメータ化されたテストの目的は、重複したテストを排除することです。パラメータ化されたテストにパラメータを渡すには、DataRow 属性と DynamicData 属性の 2 つの方法があります。

DataRow では、定数と配列しか渡すことができないという問題があります。参照型を渡すことはできません。参照型を渡そうとすると、次のコンパイル時エラーが発生します:

ここで、DynamicData 属性の出番です。テスト データ ジェネレーター メソッド (またはプロパティ) を指定します。このジェネレーター メソッドは、テスト パラメーター配列のリストを返します。リスト内の各バケットは、異なるテスト ランです。

以下は、GetTestData という静的テスト メソッドを指定して、DynamicData 属性を単体テストに追加する方法を示しています。 :

[DynamicData(nameof(GetTestData), DynamicDataSourceType.Method)] //arrange
[TestMethod()]
public void TestMathOps(decimal a, decimal b, Func<decimal, decimal, decimal> calculatorOperation, decimal expectedValue)
{
	//act
	var actual = calculatorOperation(a, b);

	//assert
	Assert.AreEqual(expectedValue, actual);
}
Code language: C# (cs)

これが GetTestData です テストデータ生成メソッド:

private static IEnumerable<object[]> GetTestData() 
{
	return new List<object[]>()
	{
		new object[]{ 1.2m, 2.3m, (Func<decimal, decimal, decimal>)Calculator.Add, 3.5m },
		new object[]{ 1.5m, 0.5m, (Func<decimal, decimal, decimal>)Calculator.Subtract, 1.0m },
		new object[]{ 1.5m, 2.0m, (Func<decimal, decimal, decimal>)Calculator.Multiply, 3.0m }
	};
}
Code language: PHP (php)

各 object[] は異なるテスト ランです。この例では、decimal パラメーターは参照型を渡す例です。 Func パラメーターは、パラメーター化されたテストに関数を渡す例です。

テストを実行すると、次のテスト結果が得られます。ご覧のとおり、3 セットのパラメーターを使用してテストを実行しました。

Test has multiple result outcomes
   4 Passed

Results

    1)  TestMathOps
      Duration: 12 ms

    2)  TestMathOps (1.2,2.3,System.Func`3[System.Decimal,System.Decimal,System.Decimal],3.5)
      Duration: 4 ms

    3)  TestMathOps (1.5,0.5,System.Func`3[System.Decimal,System.Decimal,System.Decimal],1.0)
      Duration: < 1 ms

    4)  TestMathOps (1.5,2.0,System.Func`3[System.Decimal,System.Decimal,System.Decimal],3.0)
      Duration: < 1 msCode language: plaintext (plaintext)

DynamicData には多くのテストの匂いがあります。使用は自己の判断で行ってください

上記の DynamicData の例を見て、「コードのにおいアラーム」が鳴り始めたかもしれませんが、それには正当な理由があります。それを使用すると、多くのテスト匂いにつながります . DynamicData の使用は実用的な選択です。これは、テストの重複とテストの匂いのトレードオフです。与えられた状況では意味があるかもしれません。

以下にテストの匂いをいくつか挙げます。

  • においのテスト #1 – テスト ケースの 1 つが失敗すると、どのテストが失敗したかについての役に立たない情報が得られます。

たとえば、Calculator.Multiply() に対するテストが失敗したとします。これにより、次のテスト結果が生成されます:

TestMathOps (1.5,2.0,System.Func`3[System.Decimal,System.Decimal,System.Decimal],3.0)
      Duration: 21 ms

      Message: 
        Assert.AreEqual failed. Expected:<3.0>. Actual:<-0.5>. Code language: plaintext (plaintext)

どのテスト ケースが失敗したかは簡単にわかりますか?あまり。テスト データ ジェネレーター メソッドを調べて、いくつかのパラメーターをテスト ケースに一致させることによってのみ、それを知ることができます。

代わりに個別の単体テストがあれば、Multiply テスト ケースが失敗したことが明示的に示されます。

  • においのテスト #2 – アレンジのステップは、テストの外で行われます。理想的には、アレンジ - アクト - アサートのすべてのステップがテストに含まれ、理解しやすくなります。

注:これは、ExpectedException 属性がテストの匂いであり、MSTestv2 で Assert.ThrowsException を導入した理由と同じ理由です。

  • においのテスト #3 – DynamicData は過度に複雑なコードにつながります。

DynamicData は理解するのが難しく、間接的で複雑です。テスト データ ジェネレーター メソッド (間接) の名前を渡します。これは object[] の (間接) リストを返します。各 object[] は、異なるパラメーターを含む異なるテスト ケースです。 DynamicData アプローチを見るだけでは、直感的ではありません。さらに、object[] は、パラメーターの安全性と型の安全性を同時に取り除きます。コンパイラは、正しい数のパラメーターまたは適切な型のパラメーターで object[] を渡すことを強制できない可能性があります。

これにより、理解したり保守したりするのが非常に難しい、過度に複雑なコードのすべてのボックスがチェックされます。ただし、重複したテストを取り除くために、この過度の複雑さが価値があるかどうかを判断する必要があります。