В моих модульных тестах я часто выбрасываю произвольные значения в свой код, чтобы посмотреть, что он делает. Например, если я знаю, что foo(1, 2, 3)
должен возвращать 17, я мог бы написать это:
assertEqual(foo(1, 2, 3), 17)
Эти числа являются чисто произвольными и не имеют более широкого значения (они не являются, например, граничными условиями, хотя я также проверяю их). Я бы изо всех сил пытался придумать хорошие имена для этих чисел, и писать что-то вроде этого const int TWO = 2;
, очевидно, бесполезно. Можно ли писать такие тесты, или я должен разделить числа на константы?
В Все ли магические числа созданы одинаково? мы узнали, что магические числа в порядке, если смысл очевиден из контекста, но в этом случае числа фактически не имеют никакого значения.
unit-testing
Kevin
источник
источник
1, 2, 3
это индексы трехмерного массива, в которых вы ранее сохранили значение17
, то я думаю, что этот тест будет отличным (если у вас есть и отрицательные тесты). Но если это результат вычислений, вы должны убедиться, что любой, кто читает этот тест, поймет, почему такfoo(1, 2, 3)
должно быть17
, и магические числа, вероятно, не достигнут этой цели.const int TWO = 2;
это даже хуже, чем просто использовать2
. Это соответствует формулировке правила с намерением нарушить его дух.foo
, это ничего не будет значить, и поэтому параметры. Но на самом деле, я уверен , что функция не имеет это имя, и эти параметры не имеют названияbar1
,bar2
иbar3
. Сделайте более реалистичный пример, в котором имена имеют смысл, и тогда будет гораздо разумнее обсудить, нужно ли имени для тестовых данных тоже имя.Ответы:
Когда у вас действительно есть числа, которые не имеют никакого значения вообще?
Обычно, когда числа имеют какое-либо значение, вы должны назначить их локальным переменным тестового метода, чтобы сделать код более читабельным и понятным. Имена переменных должны как минимум отражать значение переменной, а не обязательно ее значение.
Пример:
Обратите внимание, что первая переменная не названа
HUNDRED_DOLLARS_ZERO_CENT
, ноstartBalance
для обозначения значения переменной, но не в том, что ее значение каким-либо образом особенное.источник
0.05f
дляint
. :)const
переменной.calculateCompoundInterest
? Если это так, то дополнительная проверка является доказательством того, что вы прочитали документацию для функции, которую вы тестируете, или, по крайней мере, скопировали имена, предоставленные вам вашей IDE. Я не уверен, насколько это говорит читателю о намерениях кода, но если вы передадите параметры в неправильном порядке, они, по крайней мере, смогут сказать, что было задумано.Если вы используете произвольные числа только для того, чтобы посмотреть, что они делают, то, что вы действительно ищете, это, вероятно, случайно сгенерированные тестовые данные или тестирование на основе свойств.
Например, Hypothesis - это классная библиотека Python для такого рода тестирования, основанная на QuickCheck .
Идея состоит в том, чтобы не ограничивать себя собственными значениями, а выбирать случайные значения, которые можно использовать для проверки соответствия ваших функций их спецификациям. В качестве важного примечания, эти системы, как правило, запоминают любые входные данные, которые дают сбой, и затем гарантируют, что эти входные данные всегда проверяются в будущем.
Пункт 3 может сбивать с толку некоторых людей, поэтому давайте уточним. Это не означает, что вы утверждаете точный ответ - это очевидно невозможно сделать для произвольного ввода. Вместо этого вы утверждаете что-то о свойстве результата. Например, вы можете утверждать, что после добавления чего-либо в список оно становится непустым или что само сбалансированное двоичное дерево поиска фактически сбалансировано (с использованием любых критериев, которые имеет конкретная структура данных).
В целом, выбор произвольных чисел сам по себе, вероятно, довольно плохой - он на самом деле не добавляет всей ценности и вводит в заблуждение любого, кто его читает. Автоматически генерировать кучу случайных тестовых данных и эффективно их использовать - это хорошо. Поиск гипотезы или библиотеки, подобной QuickCheck, для вашего языка - это, вероятно, лучший способ достичь ваших целей, оставаясь при этом понятным для других.
источник
foo
такое вычисления) ...? Если бы вы были на 100% уверены, что ваш код дает правильный ответ, то вы просто поместили бы этот код в программу, а не тестировали его. Если нет, то вам нужно протестировать тест, и я думаю, что все видят, куда это идет.d
дням, расчет поd
дням + 1 месяц должен быть выше известной месячной процентной ставки) и т. Д.Ваше имя модульного теста должно обеспечивать большую часть контекста. Не из значений констант. Название / документация для теста должны содержать соответствующий контекст и объяснение того, какие магические числа присутствуют в тесте.
Если этого недостаточно, небольшая часть документации должна быть в состоянии предоставить его (через имя переменной или строку документации). Имейте в виду, что сама функция имеет параметры, которые, как мы надеемся, имеют значимые имена. Копировать их в свой тест для именования аргументов довольно бессмысленно.
И, наконец, если ваши юнит-тесты достаточно сложны, что это сложно / не практично, вы, вероятно, имеете слишком сложные функции и можете подумать, почему это так.
Чем более небрежно вы пишете тесты, тем хуже будет ваш реальный код. Если вы чувствуете необходимость назвать свои значения теста, чтобы сделать тест понятным, это настоятельно рекомендует вашему фактическому методу требовать лучшего наименования и / или документации. Если вы обнаружите необходимость называть константы в тестах, я выясню, зачем вам это нужно - скорее всего, проблема не в самом тесте, а в его реализации.
источник
Это сильно зависит от функции, которую вы тестируете. Я знаю много случаев, когда отдельные числа сами по себе не имеют особого значения, но контрольный пример в целом построен продуманно и поэтому имеет особое значение. Вот что нужно каким-то образом документировать. Например, если на
foo
самом деле это метод,testForTriangle
который решает, могут ли три числа быть действительными длинами краев треугольника, ваши тесты могут выглядеть следующим образом:и так далее. Вы можете улучшить это и превратить комментарии в сообщение, параметр
assertEqual
которого будет отображаться при сбое теста. Затем вы можете улучшить это и преобразовать в тест, управляемый данными (если это поддерживается вашей средой тестирования). Тем не менее, вы делаете себе одолжение, если вносите в код примечание, почему вы выбрали эти цифры и какое из различных поведений вы тестируете в каждом конкретном случае.Конечно, для других функций отдельные значения параметров могут иметь большее значение, поэтому использование бессмысленного имени функции, например,
foo
когда спрашиваете, как работать со значением параметров, вероятно, не лучшая идея.источник
Почему мы хотим использовать именованные константы вместо чисел?
Если вы напишете несколько модульных тестов, каждый из которых имеет ассортимент из 3 чисел (startBalance, процент, годы) - я просто упаковал бы значения в модульный тест как локальные переменные. Наименьшая сфера, где они принадлежат.
Если вы используете язык, который допускает именованные параметры, это, конечно, излишне. Там я просто упаковал бы необработанные значения в вызов метода. Я не могу представить какой-либо рефакторинг, делающий это утверждение более кратким:
Или используйте тестирующий фреймворк, который позволит вам определять тестовые случаи в некотором массиве или формате карты:
источник
Числа используются для вызова метода, поэтому, безусловно, приведенная выше предпосылка неверна. Вам может быть все равно, какие цифры, но это не относится к делу. Да, вы могли бы определить, для чего используются числа в некоторых IDE-мастерах, но было бы гораздо лучше, если бы вы просто дали имена значений - даже если они просто соответствуют параметрам.
источник
assertEqual "Returned value" (makeKindInt 42) (runTest "lvalue_operators")
). В этом примере42
это просто значение заполнителя, которое создается кодом в тестовом сценарии с именемlvalue_operators
и затем проверяется, когда оно возвращается сценарием. Это не имеет никакого значения, за исключением того, что одно и то же значение встречается в двух разных местах. Какое здесь подходящее имя может дать какое-либо полезное значение?Если вы хотите протестировать чистую функцию на одном наборе входных данных, которые не являются граничными условиями, то вы почти наверняка захотите протестировать ее на всем наборе входных данных, которые не являются (и являются) граничными условиями. И для меня это означает, что должна быть таблица значений для вызова функции и цикл:
Инструменты, подобные тем, которые предлагаются в ответе Даннно, могут помочь вам составить таблицу значений для тестирования.
bar
,baz
Иblurf
должны быть заменены на значимые имена , как описано в ответ Филиппа в .(Здесь можно утверждать общий принцип: числа не всегда являются «магическими числами», которым нужны имена; вместо этого числа могут быть данными . Если имеет смысл поместить ваши числа в массив, возможно, в массив записей, то это, вероятно, данные И наоборот, если вы подозреваете, что у вас могут быть данные, попробуйте поместить их в массив и получить больше.)
источник
Тесты отличаются от производственного кода, и, по крайней мере, в модульных тестах, написанных на Spock, которые являются короткими, и у меня нет проблем с использованием магических констант.
Если тест состоит из 5 строк и следует базовой схеме «дано / когда / тогда», извлечение таких значений в константы только сделает код длиннее и сложнее для чтения. Если логика звучит так: «Когда я добавляю пользователя по имени Смит, я вижу, что пользователь Смит вернулся в список пользователей», нет смысла извлекать «Смит» из константы.
Это, конечно, применимо, если вы можете легко сопоставить значения, используемые в блоке «задано» (настройка), со значениями, найденными в блоках «когда» и «затем». Если ваша тестовая установка отделена (в коде) от места, где используются данные, возможно, лучше использовать константы. Но поскольку тесты лучше всего автономны, настройка обычно близка к месту использования, и применяется первый случай, означающий, что магические константы в этом случае вполне приемлемы.
источник
Во-первых, давайте согласимся, что «модульный тест» часто используется для охвата всех автоматических тестов, которые пишет программист, и что бессмысленно спорить о том, как называть каждый тест….
Я работал над системой, в которой программное обеспечение брало много входных данных и разработало «решение», которое должно было выполнять некоторые ограничения при оптимизации других чисел. Правильных ответов не было, поэтому программное обеспечение должно было дать разумный ответ.
Это было сделано с помощью множества случайных чисел, чтобы получить отправную точку, а затем с помощью «альпиниста холма», чтобы улучшить результат. Это было выполнено много раз, выбирая лучший результат. Генератор случайных чисел может быть заполнен так, что он всегда выдает одинаковые числа в одном и том же порядке, поэтому, если тест устанавливает начальное число, мы знаем, что результат будет одинаковым при каждом запуске.
У нас было много тестов, которые сделали вышеупомянутое, и проверили, что результаты были такими же, это сказало нам, что мы не изменили то, что эта часть системы сделала по ошибке при рефакторинге и т. Д. Это не сказало нам ничего о правильности что сделала эта часть системы.
Эти тесты были дорогостоящими в обслуживании, так как любое изменение в оптимизирующем коде могло нарушить тесты, но они также обнаружили некоторые ошибки в гораздо более крупном коде, который предварительно обрабатывал данные и постобработал результаты.
Поскольку мы «издевались» над базой данных, вы могли бы назвать эти тесты «модульными тестами», но «блок» был довольно большим.
Часто, когда вы работаете в системе без тестов, вы делаете что-то подобное выше, так что вы можете подтвердить, что ваш рефакторинг не меняет вывод; надеюсь, лучшие тесты написаны для нового кода!
источник
Я думаю, что в этом случае числа следует называть произвольными числами, а не магическими числами, и просто прокомментируйте строку как «произвольный контрольный пример».
Конечно, некоторые Магические Числа также могут быть произвольными, например, для уникальных «дескрипторных» значений (которые, конечно, должны быть заменены именованными константами), но также могут быть заранее вычисленными константами, такими как «воздушная скорость порожнего европейского воробья в Фарлонгах за две недели», где числовое значение вставляется без комментариев или полезного контекста.
источник
Я не буду рисковать настолько, чтобы сказать однозначно да / нет, но вот некоторые вопросы, которые вы должны задать себе, решая, все ли в порядке или нет.
Если цифры ничего не значат, то почему они там в первую очередь? Могут ли они быть заменены чем-то другим? Можете ли вы сделать проверку на основе вызовов методов и потоков вместо утверждений значений? Рассмотрим что-то похожее на
verify()
метод Mockito, который проверяет, были ли сделаны определенные вызовы метода для имитации объектов, вместо того, чтобы фактически утверждать значение.Если числа действительно что-то значат, то они должны быть присвоены переменным, которые имеют соответствующие имена.
Запись числа ,
2
какTWO
могло бы быть полезным в определенных условиях, и не столько в других контекстах.assertEquals(TWO, half_of(FOUR))
имеет смысл для того, кто читает код. Сразу понятно, что вы тестируете.assertEquals(numCustomersInBank(BANK_1), TWO)
, то это не делает этого особого смысла. Почему жеBANK_1
содержит два клиента? Для чего мы тестируем?источник