Это несколько ортогонально содержанию вашего вопроса, но напрямую касается вопроса, поставленного в заголовке.
Идиоматическое функциональное программирование включает в себя в основном фрагменты кода без побочных эффектов, что в целом облегчает модульное тестирование.Определение модульного теста обычно включает в себя утверждение логического свойства тестируемой функции, а не создание большого количества хрупких лесов просто для создания подходящей тестовой среды.
В качестве примера, скажем, мы тестируем extendEnv
и lookupEnv
функционируют как часть переводчика.Хороший модульный тест для этих функций будет проверять, что если мы дважды расширяем среду с одной и той же переменной, привязанной к разным значениям, то lookupEnv
.
возвращает только самое последнее значение.свойство может выглядеть следующим образом:
test =
let env = extendEnv "x" 5 (extendEnv "x" 6 emptyEnv)
in lookupEnv env "x" == Just 5
Этот тест дает нам некоторую уверенность и не требует каких-либо настроек или демонтажа, кроме создания значения env
, которое мы заинтересованы в тестировании.Однако тестируемые значения очень специфичны.Это тестирует только одну конкретную среду, поэтому небольшая ошибка может легко ускользнуть.Мы бы предпочли сделать более общее утверждение: для всех переменных x
и значений v
и w
среда env
расширяется дважды с x
, связанным с v
после x
, связанным с w
, lookupEnv env x == Just w
.
В общем, нам нужно формальное доказательство (возможно, механизированное с помощью помощника по доказательству, такого как Кок, Агда или Изабель), чтобы показать, что свойство, подобное этому, имеет место.Однако мы можем стать намного ближе, чем указывать значения тестов, используя QuickCheck , библиотеку, доступную для большинства функциональных языков, которая генерирует большое количество произвольных входных данных теста для свойств, которые мы определяем как булевы функции:
prop_test x v w env' =
let env = extendEnv x v (extendEnv x w env')
in lookupEnv env x == Just w
По приглашению QuickCheck может сгенерировать произвольные входные данные для этой функции и посмотреть, остается ли она верной для всех из них:
*Main> quickCheck prop_test
+++ OK, passed 100 tests.
*Main> quickCheckWith (stdArgs { maxSuccess = 1000 }) prop_test
+++ OK, passed 1000 tests.
QuickCheck использует очень хорошую (и расширяемую) магию для созданияэти произвольные значения, но это функциональное программирование, которое делает эти значения полезными.Делая побочные эффекты исключением (извините), а не правилом, модульное тестирование становится не столько задачей ручного задания контрольных примеров, сколько вопросом утверждения обобщенных свойств поведения ваших функций.
Этот процесс вас часто удивляет.Рассуждения на этом уровне дают вашему разуму дополнительные шансы заметить недостатки в вашем дизайне, повышая вероятность того, что вы обнаружите ошибки еще до того, как запустите свой код.