-- This file demonstrates testing using QuickCheck and shows common pitfalls
-- It was not used in 2016, but may still be useful!
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