Петля внутри unit test

Можно ли создать цикл внутри unit test?

Мой метод возвращает IEnumerable<IEnumerable>, я хотел бы unit test эту логику, где создается IEnumerable<IEnumerable>. В принципе, я хочу проверить, соответствует ли количество элементов в IEnumerable.

Я не могу найти альтернативный способ протестировать внутренний IEnumerable без инструкции цикла. Пожалуйста, дайте мне знать, если это хорошая практика.

Ответ 1

Нет технической причины, по которой вы не можете этого сделать. Вы можете иметь несколько утверждений Assert в unit test. Наличие оператора Assert в цикле - это просто сокращенный способ иметь несколько тестов Assert в тесте.

Однако некоторые люди думают, что в unit test должен быть только один оператор Assert.

Я лично не согласен - я думаю, что тест должен протестировать одну вещь - и для этого иногда вам может потребоваться более одного утверждения Assert.

Если ваш метод возвращает IEnumerable для Product, и каждый Product содержит IEnumerable of Color, тогда я думаю, что следующий тест в порядке:

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

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

Однако вам нужно знать, что если IEnumerable содержит 0 элементов, цикл никогда не будет выполнять какие-либо утверждения Assert, и ваш unit test будет "проходить", тогда как вы, вероятно, намеревались его сбой.

Чтобы исправить эту ситуацию, вы можете провести отдельный тест, убедившись, что в IEnumerable содержится более 0 элементов (т.е. что GetProducts действительно возвращает некоторые продукты):

Assert.IsNotEmpty(products);

Ответ 2

Одна из причин избежать написания цикла в тесте заключалась в том, чтобы держать анализ кратким и читаемым с первого взгляда. Поскольку вы отметили вопрос с помощью 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" >

но передается, если вы измените бит на "bat".

Книга xUnit Test Patterns: тестовый код рефакторинга Джерард Мезарос

имеет много замечательных ответов на такие вопросы, как ваш.

Ответ 3

Да, вы можете иметь петли в unit test, но с осторожностью. Как уже упоминалось Alex York, петли приемлемы, если вы протестируете одну вещь; т.е. одно ожидание.

Если вы используете циклы, я рекомендую вам выполнить две вещи:

  • Как уже упоминалось выше, проверьте непустой набор итераций. Итерация над пустым множеством является ложным положительным. Ложные положительные результаты - это ошибка всего автоматического тестирования, потому что никто не проверяет зеленый результат.
  • Включить описание теста, описывающее текущую итерацию. Как минимум, включите индекс итерации.

Вот пример из моего тестирования свойства Greater Than объекта.

[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]

Описание теста на уровне итерации - это так: Failures, например:

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

Вопрос/дебаты о моем коде выше:

Я проверяю одну вещь?

Я утверждаю 4 различных метода сравнения внутри объекта, которые можно было бы утверждать как тестирование 4 вещей, а не один. Счетчик больше, чем больше, чем и что все методы, которые делают эту оценку, должны быть согласованными.