Haskell Language
Быстрая проверка
Поиск…
Объявление собственности
В своем простейшем случае свойство является функцией, которая возвращает Bool
.
prop_reverseDoesNotChangeLength xs = length (reverse xs) == length xs
Свойство объявляет высокоуровневый инвариант программы. Тестер QuickCheck проверит функцию со 100 случайными входами и проверит, что результат всегда True
.
По соглашению функции, которые являются свойствами, имеют имена, которые начинаются с prop_
.
Проверка одного объекта
Функция quickCheck
проверяет свойство на 100 случайных входах.
ghci> quickCheck prop_reverseDoesNotChangeLength
+++ OK, passed 100 tests.
Если свойство некорректно для некоторого ввода, quickCheck
распечатывает контрпример.
prop_reverseIsAlwaysEmpty xs = reverse xs == [] -- plainly not true for all xs
ghci> quickCheck prop_reverseIsAlwaysEmpty
*** Failed! Falsifiable (after 2 tests):
[()]
Проверка всех свойств в файле
quickCheckAll
- это помощник шаблона Haskell, который находит все определения в текущем файле, имя которого начинается с prop_
и проверяет их.
{-# LANGUAGE TemplateHaskell #-}
import Test.QuickCheck (quickCheckAll)
import Data.List (sort)
idempotent :: Eq a => (a -> a) -> a -> Bool
idempotent f x = f (f x) == f x
prop_sortIdempotent = idempotent sort
-- does not begin with prop_, will not be picked up by the test runner
sortDoesNotChangeLength xs = length (sort xs) == length xs
return []
main = $quickCheckAll
Обратите внимание, что требуется строка return []
. Он делает текстовые надписи над этой строкой видимыми для Template Haskell.
$ runhaskell QuickCheckAllExample.hs
=== prop_sortIdempotent from tree.hs:7 ===
+++ OK, passed 100 tests.
Произвольное генерирование данных для пользовательских типов
Класс Arbitrary
предназначен для типов, которые могут быть случайно сгенерированы QuickCheck.
Минимальная реализация Arbitrary
- это arbitrary
метод, который выполняется в монаде Gen
для получения случайного значения.
Вот пример Arbitrary
для следующего типа данных непустых списков.
import Test.QuickCheck.Arbitrary (Arbitrary(..))
import Test.QuickCheck.Gen (oneof)
import Control.Applicative ((<$>), (<*>))
data NonEmpty a = End a | Cons a (NonEmpty a)
instance Arbitrary a => Arbitrary (NonEmpty a) where
arbitrary = oneof [ -- randomly select one of the cases from the list
End <$> arbitrary, -- call a's instance of Arbitrary
Cons <$>
arbitrary <*> -- call a's instance of Arbitrary
arbitrary -- recursively call NonEmpty's instance of Arbitrary
]
Использование импликации (==>) для проверки свойств с предварительными условиями
prop_evenNumberPlusOneIsOdd :: Integer -> Property
prop_evenNumberPlusOneIsOdd x = even x ==> odd (x + 1)
Если вы хотите проверить, что свойство выполнено, если выполнено предварительное условие, вы можете использовать оператор ==>
. Обратите внимание: если очень маловероятно, чтобы произвольные входы соответствовали предварительному условию, QuickCheck может отказаться раньше.
prop_overlySpecific x y = x == 0 ==> x * y == 0
ghci> quickCheck prop_overlySpecific
*** Gave up! Passed only 31 tests.
Ограничение размера тестовых данных
Трудно проверить функции с плохой асимптотической сложностью, используя quickcheck, поскольку случайные входы обычно не ограничены размером. Добавив верхнюю границу размера ввода, мы все еще можем проверить эти дорогостоящие функции.
import Data.List(permutations)
import Test.QuickCheck
longRunningFunction :: [a] -> Int
longRunningFunction xs = length (permutations xs)
factorial :: Integral a => a -> a
factorial n = product [1..n]
prop_numberOfPermutations xs =
longRunningFunction xs == factorial (length xs)
ghci> quickCheckWith (stdArgs { maxSize = 10}) prop_numberOfPermutations
Используя quickCheckWith
с модифицированной версией stdArgs
мы можем ограничить размер входов не более 10. В этом случае, когда мы создаем списки, это означает, что мы генерируем списки размером до 10. Наша функция перестановок не занимать слишком много времени для выполнения этих коротких списков, но мы все же можем быть достаточно уверенными в правильности нашего определения.