Я хотел спросить вас, в каких случаях имеет смысл проводить модульное тестирование статически типизированного функционального кода, как написано на haskell, scala, ocaml, nemerle, f # или haXe (последнее, что меня действительно интересует, но я хотел использовать знания крупных сообществ).
Я спрашиваю об этом, потому что из моего понимания:
Один из аспектов модульных тестов - иметь спецификации в работоспособном виде. Однако при использовании декларативного стиля, который непосредственно отображает формализованные спецификации для семантики языка, это даже на самом деле можно выразить спецификации в работоспособной форме в отдельном пути, который добавляет значение?
Более очевидным аспектом модульных тестов является отслеживание ошибок, которые невозможно выявить с помощью статического анализа. Учитывая, что функционально безопасный код типа является хорошим инструментом для кодирования, очень близкого к тому, что понимает ваш статический анализатор, может показаться, что вы можете сместить большую безопасность в сторону статического анализа. Однако простая ошибка, такая как использование
x
вместоy
(обе являются координатами) в вашем коде, не может быть покрыта. OTOH такая ошибка также может возникнуть при написании тестового кода, поэтому я не уверен, стоит ли это усилий.Модульные тесты вводят избыточность, что означает, что при изменении требований код, реализующий их, и тесты, охватывающие этот код, должны быть изменены. Эти накладные расходы, конечно, постоянны, так что можно утверждать, что это не имеет значения. На самом деле, в таких языках, как Ruby, это на самом деле не сравнивается с преимуществами, но, учитывая, что статически типизированное функциональное программирование охватывает большую часть базовых модульных тестов, создается ощущение, что это постоянные издержки, которые можно просто уменьшить без штрафа.
Из этого я могу сделать вывод, что модульные тесты несколько устарели в этом стиле программирования. Конечно, такое утверждение может привести только к религиозным войнам, поэтому позвольте мне свести это к простому вопросу:
Когда вы используете такой стиль программирования, в какой степени вы используете модульные тесты и почему (какое качество вы надеетесь получить для своего кода)? Или наоборот: у вас есть критерии , по которым вы можете претендовать на единицу статически типизированного функционального кода, покрытые статическим анализатором и , следовательно , без необходимости покрытия модульного тестирования?
Ответы:
Если у вас есть спецификации, которые можно сопоставить непосредственно с объявлениями функций - хорошо. Но обычно это два совершенно разных уровня абстракций. Модульные тесты предназначены для тестирования отдельных фрагментов кода, написанных в виде белых тестов того же разработчика, который работает над этой функцией. Спецификации обычно выглядят так: «Когда я введу это значение здесь и нажму на эту кнопку, то и это должно произойти». Как правило, такая спецификация приводит к разработке и тестированию более чем одной функции.
Ваше заблуждение заключается в том, что модульные тесты на самом деле предназначены для поиска ошибок в вашем коде из первых рук - это неправда, по крайней мере, это только частично верно. Они сделаны для того, чтобы предотвратить появление ошибок позже, когда ваш код развивается. Поэтому, когда вы сначала проверили свою функцию и работали модульный тест (с правильно установленными «x» и «y»), а затем при рефакторинге вы используете x вместо y, тогда модульный тест покажет это вам.
В разработке большинство систем безопасности полагаются на избыточность. Например, два перерыва в автомобиле, избыточный парашют для дайвера и т. Д. Та же идея применима к юнит-тестам. Конечно, недостаток кода заключается в большем количестве кода, который можно изменить при изменении требований. Поэтому особенно в модульных тестах важно сохранять их СУХИМЫМИ (следуйте принципу «Не повторяйте себя»). В языке со статической типизацией вам, возможно, придется написать несколько меньших модульных тестов, чем в языке со слабой типизацией. Особенно «формальные» тесты могут не понадобиться - это хорошо, так как это дает вам больше времени для работы над важными юнит-тестами, которые проверяют существенные вещи. И не думайте, что только потому, что вы статические типы, вам не нужны юнит-тесты, все еще есть много места, чтобы вносить ошибки при рефакторинге.
источник
Маловероятно, что вы сможете полностью выразить свои спецификации в виде ограничений типа.
На самом деле, одно из основных преимуществ этого стиля заключается в том, что чистые функции легче тестировать модулем: нет необходимости настраивать внешнее состояние или проверять его после выполнения.
Часто спецификация (или ее часть) функции может быть выражена как свойства, связывающие возвращаемое значение с аргументами. В этом случае использование QuickCheck (для Haskell) или ScalaCheck (для Scala) может позволить вам записать эти свойства как выражения языка и проверить их на случайность ввода.
источник
Вы можете рассматривать модульные тесты как пример того, как использовать код, вместе с описанием того, почему он полезен.
Вот пример, в котором дядя Боб был достаточно любезен, чтобы присоединиться ко мне в «Игре жизни» Джона Конвея . Я думаю, что это отличное упражнение для такого рода вещей. Большинство тестов являются полностью системными, тестируют всю игру, но первый тест проверяет только одну функцию - ту, которая вычисляет соседей вокруг ячейки. Вы можете видеть, что все тесты написаны в декларативной форме, а поведение, которое мы ищем, четко прописано.
Также возможно смоделировать функции, используемые в функциях; либо передавая их в функцию (эквивалент внедрения зависимостей), либо с помощью фреймворков, таких как Midje Брайана Марика .
источник
Да, модульные тесты уже имеют смысл со статически типизированным функциональным кодом. Простой пример:
Вы можете форсировать
prop_encode
статическим типом.источник