Unity 6.3
0 онлайн 20 гостей 3 в системе
Вход

15. Тестовые случаи

Цели обучения

В этом разделе будут рассмотрены атрибуты [TestCase] и аналогичные атрибуты NUnit и как работать с ними в UnityTests.

Введение и мотивация

NUnit имеет несколько инструментов для параметризированных тестов, которые могут быть использованы для определения тестовых случаев с различными параметрами. Это может резко сократить количество повторяющегося кода и сделать тест чистым для использования.

Пример параметризированного теста с использованием атрибута [TestCase]:

[Test]
[TestCase(49, "a string", true)]
[TestCase(9, "something", false)]
public void MyTest(int firstValue, string secondValue, bool expectedOutcome)
{
 ...
}  

Это создаст два теста, каждый с разным входом в тело метода.

В дополнение к атрибуту [TestCase], NUnit также имеет атрибут [Values], который указывает набор значений на каждом отдельном входе. Примером такого атрибута является:

[Test]
public void MyTest([Values(49, 9)]int firstValue, [Values("a string", "something")]string secondValue)
{
 ...
}  

При указании нескольких входных параметров, они рассматриваются как комбинационные. Это означает, что каждая комбинация из них будет проверена. Для вышеприведенного примера это приведет к 4 случаям:

MyTest(49, "a string")
MyTest(49, "something")
MyTest(9, "a string")
MyTest(9, "something") 

Это может легко взорваться во многих комбинациях. Комбинации могут быть не все ценные и просто тратить время, так что используйте это с осторожностью.

Для обоих [TestCase] и [Values] атрибутов, существует более динамичная версия, называемая [TestCaseSource] и [ValueSource] соответственно. Каждый из них принимает статический метод или массив и возвращает коллекцию объектов.

Из этих 4 методов, [ValueSource] атрибут в настоящее время является единственным, поддерживаемым [UnityTest]Поскольку это приведет к комбинированным тестам, если несколько аргументов с [ValueSource] заданы, то для теста с несколькими аргументами рекомендуется создать структуру тестового случая. Выглядеть это может так:

[UnityTest]
public IEnumerator AddAsyncCalculatesCorrectValue([ValueSource(nameof(TestCases))] TestCase testCase)
{
 ...
}

private static IEnumerable TestCases()
{
 yield return new TestCase {value1 = 4, value2 = "a string"};
 yield return new TestCase {value1 = 8, value2 = "another string"};
}

public struct TestCase
{
 public int value1;
 public string value2;

 public override string ToString()
 {
  return $"{value1}, {value2}";
 }
}

2010 год

В течение образец 15_TestCases класс с несложной математикой. У него два метода:

  • Add, которая берёт два целых числа и суммирует их.

  • AddAsync также суммирует два целых числа, но делает это асинхронно, возвращая IEnumerator

Задача состоит в том, чтобы добавить тесты для двух методов. Метод AddAsync сначала возвращает результат после нескольких кадров, так что это будет лучше всего подходить для [UnityTest]. Обратите внимание, что не достаточно возвращать IEnumerator, так как тестовый фреймворк сейчас не поддерживает вложенные возвраты. Вместо этого создайте цикл и продвигайтесь по каждому элементу, пока работа не будет завершена. На каждой итерации цикла while тест должен возвращать null.

Подсказки

  • ToString() реализация в структуре нужна, чтобы в дереве тестового раннера отображались понятные сведения. Без неё для каждого случая в качестве аргумента теста показывалось бы просто имя структуры.

Решение

Решение для этого упражнения можно найти в примере 15_TestCases_Solution. Тесты для обоих методов могут быть реализованы следующим образом:

[Test]
[TestCase(24, 80, 104)]
[TestCase(10, -15, -5)]
[TestCase(int.MaxValue, 10, int.MinValue + 9)]
public void AddCalculatesCorrectValue(int valueA, int valueB, int expectedResult)
{
 var myClass = new MyClass();

 var result = myClass.Add(valueA, valueB);
 
 Assert.That(result, Is.EqualTo(expectedResult));
}

[UnityTest]
public IEnumerator AddAsyncCalculatesCorrectValue([ValueSource(nameof(AdditionCases))] AddCase addCase)
{
 var myClass = new MyClass();

 var enumerator = myClass.AddAsync(addCase.valueA, addCase.valueB);
 while (enumerator.MoveNext())
 {
  yield return null;
 }
 var result = enumerator.Current;
 
 Assert.That(result, Is.EqualTo(addCase.expectedResult));
}

private static IEnumerable AdditionCases()
{
 yield return new AddCase {valueA = 24, valueB = 80, expectedResult = 104};
 yield return new AddCase {valueA = 10, valueB = -15, expectedResult = -5};
 yield return new AddCase {valueA = int.MaxValue, valueB = 10, expectedResult = int.MinValue + 9};
}

public struct AddCase
{
 public int valueA;
 public int valueB;
 public int expectedResult;

 public override string ToString()
 {
  return $"{valueA} + {valueB} = {expectedResult}";
 }
}