web-dev-qa-db-ja.com

ユニットテスト内でループする

ユニットテスト内にループを作成できますか?

私のメソッドはIEnumerable<IEnumerable>を返します。IEnumerable<IEnumerable>が作成されたこのロジックをユニットテストしたいと思います。基本的に、IEnumerableの要素数が期待どおりかどうかをテストしたいと思います。

ループステートメントがないと、内部のIEnumerableをテストする別の方法を見つけることができません。これが良い習慣であるかどうか私に知らせてください。

20
Shankar Raju

あなたがそれをすることができない技術的な理由はありません。単体テストで複数のAssertステートメントを使用できます。ループ内にAssertステートメントを含めることは、テストに複数のAssertステートメントを含めるための簡単な方法です。

ただし、単体テストには1つのAssertステートメントのみが必要であると考える人もいます。

私は個人的に同意しません-テストはテストする必要があると思います単一のこと-そしてそれを行うために、複数のAssertステートメントが必要になる場合があります。

メソッドがProductのIEnumerableを返し、各ProductにColorのIEnumerableが含まれている場合、次のテストで問題ないと思います。

[Test]
public void All_products_should_have_some_colors()
{
    var products = GetProducts();

    foreach (var product in products)
    {
        Assert.IsNotEmpty(product.Colors);
    }
}

ただし、IEnumerableに0個の要素が含まれている場合、ループはAssertステートメントを実行せず、単体テストは「合格」することに注意する必要がありますが、おそらく失敗することを意図しているはずです。

この状況を改善するために、IEnumerableに0を超える要素があることを確認する別のテストを行うことができます(つまり、GetProductsは実際にいくつかの製品を返します)。

Assert.IsNotEmpty(products);
23
Alex York

テストでループを記述しないようにする理由の1つは、テストを簡潔で一目で読みやすくするためです。質問にNUnitのタグを付け、要素数が期待どおりであることをテストしたいだけなので、 NUnit Constraints を使用してアサートを作成することを検討してください。

例えば、

IEnumerable<IEnumerable<char>> someStrings = new[] { "abc", "cat", "bit", "hat" };

Assert.That(someStrings, Has.All.With.Length.EqualTo(3).And.All.Contains("a"));

次のメッセージで失敗します:

期待:すべてのアイテムプロパティ長さが3に等しく、すべてのアイテムに「a」を含む文字列が含まれていましたが、次のとおりでした:<"abc"、 "cat"、 "bit"、 "hat">

ただし、「ビット」を「バット」に変更すると合格します。

本xUnitTest Patterns:Refactoring Test Code By Gerard Meszaros

あなたのような質問に対する多くの素晴らしい答えがあります。

8
Grokodile

はい、ユニットテストでループを作成できますが、注意が必要です。 Alex Yorkが述べたように、テストする場合はループが許容されますone thing;つまり、1つの期待。

ループを使用する場合は、次の2つのことを行うことをお勧めします必須

  1. 上記のように、空でない反復セットをテストします。空のセットを反復処理することは誤検知です。誰も緑の結果を二重にチェックしないため、偽陽性の結果はすべての自動テストの悩みの種です。
  2. 現在の反復を説明するテストの説明を含めます。少なくとも、反復インデックスを含めてください。

これは、オブジェクトのGreaterThanプロパティをテストした例です。

[Test]
public void TestCompare_XtoY_GreaterThan()
{
  int numObjects = mOrderedList.Count;
  for (int i = 1; i < numObjects; ++i)
  {
    for (int j = 0; j < i; ++j)
    {
      string testDescription = string.Format("{0} is greater than {1} which implies\n  {2}\n    is greater than\n  {3}"
                                            , i, j
                                            , mOrderedList[i], mOrderedList[j]
                                            );
      Assert.IsTrue(0 < mOrderedList[i].CompareTo(mOrderedList[j]), testDescription);
      Assert.IsTrue(0 < mOrderedList[i].Compare(mOrderedList[i], mOrderedList[j]), testDescription);
      Assert.IsTrue(0 < mOrderedList[j].Compare(mOrderedList[i], mOrderedList[j]), testDescription);
      Assert.Greater(mOrderedList[i], mOrderedList[j], testDescription);
    }
  }
}

以下を使用して、テストセットアップで注文リストが空でないことをテストします。

[SetUp]
public void GeneralTestSetup()
{
  // verify the iterated sources are not empty
  string testDescription = string.Format("The ordered list of objects must have at least 3 elements, but instead has only {0}.", mOrderedList.Count);
  Assert.IsTrue(2 < mOrderedList.Count, testDescription);
}

ループ内にも複数のアサーションがありますが、すべてのアサーションが単一の期待値をテストしています。

if i > j then mOrderedList[i] > mOrderedList[j]

反復レベルでのテストの説明は、次のような失敗を取得するためのものです。

10 is greater than 9 which implies
  TestActionMethodInfo: [Actions.File, Version=1.0.446.0, File, VerifyReadOnly]
    is greater than
  TestActionMethodInfo: [Actions.File, Version=1.0.446.0, File, Write]
Expected: True
But was:  False

ただの代わりに:

Expected: True
But was:  False

上記の私のコードに関する質問/討論:

私は1つのことをテストしていますか?

私は、オブジェクト内の4つの異なる比較方法について主張しています。これは、1つではなく4つのことをテストすると主張することができます。カウンターはより大きいよりも大きいよりも大きいこと、およびその評価を行うすべての方法は一貫している必要があります。

2
John Washburn