-- This file demonstrates testing using QuickCheck and shows common pitfalls module QuickCheck where import Data.Char import Test.QuickCheck -------------------------------------------------------------------------------- -- Check that addition is commutative -- GHCi will default the types of x and y to Integer prop_commutative x y = x + y == y + x -- Check that addition is associative prop_associative x y z = (x + y) + z == x + (y + z) -- Try enabling this type signature: -- prop_associative :: Double -> Double -> Double -> Bool -- The property does not hold for Double due to rounding errors prop_associative' :: Double -> Double -> Double -> Bool prop_associative' x y z = ((x + y) + z) ~= (x + (y + z)) where -- "almost equal" x ~= y = abs (x-y) < 0.0001 -- Make sure that you test the right thing! -- QuickCheck says the following is true: prop_reverse_wierd xs = reverse xs == xs -- (because the type of xs defaults to [()]) -- Use collect -- proper test prop_reverse xs = reverse (reverse xs) == xs where _ = xs :: [Int] -- This is how we constrain the type of arguments without giving a full -- type signature for the property -------------------------------------------------------------------------------- -- QuickCheck does not always find corner cases prop_not_1000 :: Int -> Bool prop_not_1000 x = x /= 1000 -- Common corner cases are easily found prop_not_0 :: Int -> Bool prop_not_0 x = x /= 0 prop_not_20 :: Int -> Bool prop_not_20 x = x /= 20 -------------------------------------------------------------------------------- -- Remember that properties are usually incomplete. This "bug" is only found by -- the second property above maximumBad :: [Int] -> Int maximumBad xs = 2^31-1 prop_maximum :: [Int] -> Bool prop_maximum xs = and [x <= maximumBad xs | x <- xs] prop_maximum2 :: [Int] -> Property prop_maximum2 xs = xs /= [] ==> maximumBad xs `elem` xs -------------------------------------------------------------------------------- -- value s returns the number represented by the string s as an integer -- -- Example: -- -- *Main> value "123" -- 123 -- -- Can you spot the error? value :: String -> Int value s = calculate s calculate "" = 0 calculate (c:cs) = digitToInt c + 10*calculate cs -- QuickCheck shrinks the input to a minimal counterexample. What does this -- minimal example tell us? Why does it not shrink to "11"? prop_value :: String -> Property prop_value s' = s /= "" && head s /= '0' ==> show (value s) == s where s = [d | d <- s', isDigit d] -- Remove non-digits from input -- We can use collect to get statistics about the generated values. Here we get -- information about how many generated strings are interesting (i.e. contains -- digits, and first digit is not 0). -- -- Note that the use of ==> in prop_value makes sure to only run the interesting -- tests. prop_value2 :: String -> Property prop_value2 s' | s /= "" && head s /= '0' = collect "interesting" \$ show (value s) == s | otherwise = collect "trivial" True where s = [d | d <- s', isDigit d] -- We can also make sure to make all tests interesting prop_value3 :: Int -> Property prop_value3 n = n >= 0 ==> value (show n) == n