This library can be used with QuickCheck to test polymorphic properties. It will automatically pick suitable types to instantiate the properties. Furthermore, it can avoid unnecessary test cases by prefilling special parts of the inputs. Users who manually pick types to test polymorphic functions or use polyQuickCheck
or monomorphic
in QuickCheck can switch to this library to get more efficient testing without extra effort.
Suppose we want to test the following two polymorphic properties:
prop_reverse :: Eq a => [a] -> Bool
prop_reverse l = reverse (reverse l) == l
prop_map :: Eq b => (a -> b) -> [a] -> Bool
prop_map f xs = reverse (map f xs) == map f (reverse xs)
First we need to enable several extensions:
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE DeriveGeneric #-}
and import the monomorphic
function:
import Text.Show.Functions
import Test.QuickCheck hiding (monomorphic)
import Test.Logarithm.TH (monomorphic)
Now invoke monomorphic
to generate the monomorphised definitions:
$(monomorphic 'prop_reverse)
$(monomorphic 'prop_map)
Two new functions prop_reverse_mono
and prop_map_mono
will be generated, which are properties that can be directly called by quickCheck
:
main :: IO ()
main = do
quickCheck prop_reverse_mono
quickCheck prop_map_mono
Here is a user defined list type with its Arbitrary
instance
data MyList a = MyNil | MyCons a (MyList a) deriving (Eq, Show)
instance Arbitrary a => Arbitrary (MyList a) where
arbitrary = foldr MyCons MyNil <$> listOf arbitrary
Then we can monomorphize functions involving this type
prop :: Eq a => MyList a -> MyList (a, Maybe a) -> Bool
$(monomorphic prop)
It also works for inductive types with functions
data State s a = State (s -> (a, s)) deriving (Show)
instance (CoArbitrary s, Arbitrary s, Arbitrary b) => Arbitrary (State s b) where
arbitrary = State <$> arbitrary
prop :: s -> State s a -> Bool
$(monomorphic prop)
See tests/Main.hs
for more examples.
Building:
cabal build
Testing:
cabal test
To see the counterexamples generated for the wrong properties:
cabal run test
To see the generated definitions, one can add the -ddump-splices
GHC option.
Install: At the time of writing, this package has not been published to the Hackage. To use it in your own testing, you have to manually include this package in your cabal.project
.
This module only exports one function
monomorphic :: Name -> Q [Dec]
Note that its type is different from the Test.QuickCheck.monomorphic
, which has type Name -> Q Exp
. This is because our monomorphic
needs to generate the definitions of those types produced by the logarithm operation. It also generates a new function with the suffix _mono
because it provides custom instances of Arbitrary
to reduce the test cases, so it cannot reuse the original function by simply specializing its type. Therefore monomorphic
must be invoked at the top level, unlike the one in QuickCheck.
When QuickCheck finds a counterexample, sometimes it will be printed in a hardly readable form because it might use the new types generated by the library. Those types are unsimplified and their names are verbose. If you find the counterexample too confusing, it might be a good idea to instantiate the function by integers and increase the number of test cases to find a readable counter example.
To test a function with function arguments, we need Show (a -> b)
to make QuickCheck
happy. Currently we don't have good ways to show a normal function. And the Fun a b
in QuickCheck
is not treated specially. Thus to make it work, we can just import Text.Show.Functions
.
DeriveGeneric
is needed to automatically derive the CoArbitrary
instances for the logarithm types. Because the logarithm types might contain Void
, this module also implicitly exports an instance of CoArbitrary Void
. It can possibly be removed in the future if the logarithm types were simplified by eliminating empty types.