Используете ли вы константы из реализации в своих тестовых случаях?

Скажем, у вас есть такой код, как это (в составленном langague, так как это не имеет значения для этого вопроса):

constant float PI = 3.14;
float getPi() 
{ 
   return PI;
}

Вы проверили бы это следующим образом:

testPiIs3point14()
{
   // Test using literal in test case
   AssertEquals( getPi(), 3.14 );
}

Или вот так:

testPiIs3Point14()
{
   // Test using constant from implementation in test case
   AssertEquals( getPi(), PI );
}

Другими словами, используете ли вы константы из вашей системы в тестах в своих тестовых случаях? Или это считается деталью реализации?

Ответы

Ответ 1

Два теста служат для разных целей.

Первый гарантирует, что getPi() всегда будет возвращать 3.14. Он охватывает как константу, так и функцию и не срабатывает, если когда-либо кто-то найдет значение PI, используемое в программном обеспечении, недостаточно точным и заменит его, скажем, 3.14159. Это может быть хорошим или плохим, в зависимости от контекста.

Пока вторая форма охватывает только функцию. Это не сработает, если кто-то изменит константу; он будет работать только в случае, если функция будет изменена, чтобы вернуть другую константу (с другим значением).

Выбор между ними зависит от цели теста. Используйте литерал, если константа никогда не должна меняться. Используйте константу для определения поведения функции: возвращайте константу - независимо от ее значения. Во втором случае тест может быть ненужным.

Ответ 2

Я искал информацию по этой же теме. До сих пор я пришел к выводу, что вы не должны использовать литералы, но вы также должны убедиться, что константа - это то, что вы ожидаете.

Если вы использовали литералы в 10 различных модульных тестах и ​​значение изменилось по какой-либо причине, тогда вам придется изменить значение во всех 10 литералах. Вы могли бы или, например, добавить дополнительную точность в PI.

Лучшей альтернативой IMHO является реализация unit test для проверки постоянного значения, которое вы ожидаете, а затем свободно используйте константу в своих других тестах.

Что-то вроде выполнения этих двух тестов:

testPiIs3point14()
{
   AssertEquals( PI, 3.14 );
}

testGetPiReturnsPi()
{
   AssertEquals( getPi(), PI );
}

PS: Проверка значения может быть не столь важной для всех констант, это может быть очень важно для некоторых. Один из примеров, о котором я могу думать, - это константы, содержащие URL-адреса или аналогичные значения.

Ответ 3

Я думаю, что это вопрос о связи между тестами и производственным кодом. Когда я впервые начал TDD, я думал, что постоянство в тестах делает тесты более тщательными. Однако теперь я думаю, что это просто приводит к более тесной связи между тестами и реализацией. Это делает его более безопасным, если вы скопируете и вставьте константу в тест? На самом деле, нет. Это просто заставляет больно изменять константу, особенно если ее копировать в несколько тестов. Эти тесты не тестируют, если они являются правильной константой, они просто проверяют, что эта константа возвращается из метода, поэтому теперь я бы определенно пошел на тест номер два.

Ответ 4

Если вы действительно обнародованы как часть открытого интерфейса, это будет два теста:

testPI() {
   AssertEquals(PI, 3.14);
}

test_getPi() {
   AssertEquals(getPi(), 3.14);
}

Однако, если PI является деталью реализации, а getPi является общедоступным средством для извлечения значения, тогда вы должны написать только test_getPi, как указано выше, и PI должно быть private без testPI unit test. Этот последний случай, вероятно, больше похож на то, что вы должны делать.

Я думаю, вы могли бы провести третий тест, например:

test_getPi_PI_AlwaysAgree() {
   AssertEquals(getPi(), PI);
}

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

Обратите внимание, что этот последний тест не говорит о том, что getPi должен возвращать 3.14, только то, что две части кода должны иметь одинаковое значение, без каких-либо указаний на то, что это значение. Чтобы утверждать, что значение того или другого должно быть определенным значением, используйте один из первых двух тестов.

Ответ 5

Определенно вторая форма, цель константы - не вводить "магическое число". Теперь, в модульных тестах, вы склонны использовать магические числа совсем немного, и это нормально.

В этом случае вы ввели константу И использовали магическое число. Я бы либо использовал константу всюду, либо вообще не использовал ее.

Ответ 6

Я думаю, что здесь есть два разных случая:

  • Если вы тестируете константу, критическую для результата вычисления, как в вашем примере, я думаю, что лучше использовать независимый тест, а не повторять использование той же константы из кода, который вы пытаетесь тестировать. Вместо того, чтобы напрямую тестировать постоянное значение, я бы проверил (например) функцию CalculateAreaOfCircle(), которая проверила бы правильность формулы Area и в то же время проверит значение PI.

  • Я думаю, что имеет смысл повторно использовать перечисления и другие константы, которые напрямую не влияют на результат критических частей кода.

Ответ 7

Я использую первую форму - даже если она дублирует значение (только дважды), это более читаемо.

[Test]
public void GetPIReturns3_14()
{
  Assert.AreEqual(3.14, testSubject.GetPI());
}

Дублирование необходимо, потому что, если вы повторно используете константу, как во втором тесте, вы на самом деле ничего не тестируете. Фактически, вы тестируете "Постоянная константа?". Этот тест никогда не потерпит неудачу. например если я изменил значение PI на 1010.1010, второй тест не потерпит неудачу.