https://begriffs.com/posts/2017-01-14-design-use-quickcheck.html
Basically I need to construct generators (Gen a) for all the types for which I need random samples for testing.
generate :: Gen a -> IO a
The generate function takes in a 'generator' and yields random samples
Another utility function that could be useful in viewing samples generated by generator.
sample :: Gen a -> IO () sample' :: Gen a -> IO [a]
The above just calls the repeatedly generator and prints the output to the stdio.
sample :: Gen a -> IO () sample gen_a = sample' gen_a 10 where sample' gen_a n | n <= 0 = return () | otherwise = do a <- generate gen_a putStrLn (show a) sample' gen_a (n-1)
Using combinators
choose :: (a, a) -> Gen a elements :: [a] -> Gen a
Becoming an instance of Arbitrary type class.
class Arbitrary a where arbitrary :: Gen a
generate $ choose (1, 2) generate $ elements [1,2,3] generate $ (arbitrary :: Gen Int)
It is simpler, easier in practice to make instances of the Arbitrary for our data types.
There is already enough supply of helper functions constructing arbitrary instances for recursive structures of basic types eg. Bool, Int, Char, String, Tuple, Lists, Maybe, Either etc..
Using applicative(<$>) and functor (<*>) since Gen is a Monad -> Applicative -> Functor (excluding the monadic laws)
data MyType = MyType { foo :: Int, , bar :: Bool, , baz :: Char } deriving (Show)
myTypeGenerator :: Gen MyType myTypeGenerator = MyType <$> arbitrary <> arbitrary <> arbitrary
generate myTypeGenerator :: IO MyType
oneof :: [Gen a] -> Gen a -- almost even distribution among the available generators frequency :: [(Int, Gen a)] -> Gen a -- frequency control/mixing sized :: (Int -> Gen a) -> Gen a -- controlling the size of the generated sample
Properties are predicates that can be checked by testing. QuickCheck represents them am Gen Result A property is a 'Gen Result' i.e generates results. All these results need to be tested OK for the property to stand good. quickCheck runs the 'property' multiple times and when all the invocations succeed, the property is considered passed.
The 'Result' object holds the facts about a test including ...
- success/failed
- retry test
- statistics about test cases
- reason for failure
- exceptions thrown
prop_commutativeAdd :: Gen Result prop_commutativeAdd = do (x, y) <- arbitrary :: Gen (Int, Int) return $ if x + y == y + x then succeeded else failed { reason = "Addition is not commutative" }
with quickCheck (Testable prop => prop -> IO ())
we can construct and invoke all testable.
Any instance of Arbitrary can generate instance of a.
Similarly...Any instance of Testable can generate a Property
Gen a ---> Arbitrary Property ---> Testable
Refer test/Spec.hs. It has all the ways a 'Testable' can be constructed.
Refer : test/Base64Test.hs
Properties for testing base64 encoding.
Using collect, classify and cover
collect :: Is useful to collect the distribution of test case inputs classify :: Helps in bucketizing, based on how we configure and displaying the distribution in those buckets cover :: We case use this to force a failure when enough sample are generated to cover all edge cases, that we are wanting. scale :: To scale up/down a generator (Gen a) by a factor (function signature: (Int -> Int) -> Gen a -> Gen a) forAll : To use a custom generator