testing - How to use SmallCheck in Haskell? -
i trying use smallcheck test haskell program, cannot understand how use library test own data types. apparently, need use test.smallcheck.series. however, find documentation extremely confusing. interested in both cookbook-style solutions , understandable explanation of logical (monadic?) structure. here questions have (all related):
if have data type
data person = snowwhite | dwarf integer
, how explainsmallcheck
valid valuesdwarf 1
throughdwarf 7
(orsnowwhite
)? if have complicatedfairytale
data structure , constructormaketale :: [person] -> fairytale
, , wantsmallcheck
make fairytale-s lists of person-s using constructor?i managed make
quickcheck
work without getting hands dirty using judicious applications ofcontrol.monad.liftm
functionsmaketale
. couldn't figure out waysmallcheck
(please explain me!).what relationship between types
serial
,series
, etc.?(optional) point of
coseries
? how usepositive
typesmallcheck.series
?(optional) elucidation of logic behind should monadic expression, , regular function, in context of smallcheck, appreciated.
if there there intro/tutorial using smallcheck
, i'd appreciate link. thank much!
update: should add useful , readable documentation found smallcheck
this paper (pdf). not find answer questions there on first look; more of persuasive advertisement tutorial.
update 2: moved question weird identity
shows in type of test.smallcheck.list
, other places separate question.
note: answer describes pre-1.0 versions of smallcheck. see this blog post important differences between smallcheck 0.6 , 1.0.
smallcheck quickcheck in tests property on part of space of possible types. difference tries exhaustively enumerate series all of "small" values instead of arbitrary subset of smallish values.
as hinted, smallcheck's serial
quickcheck's arbitrary
.
now serial
pretty simple: serial
type a
has way (series
) generate series
type function depth -> [a]
. or, unpack that, serial
objects objects know how enumerate "small" values of. given depth
parameter controls how many small values should generate, let's ignore minute.
instance serial bool series _ = [false, true] instance serial char series _ = "abcdefghijklmnopqrstuvwxyz" instance serial => serial (maybe a) series d = nothing : map (series d)
in these cases we're doing nothing more ignoring depth
parameter , enumerating "all" possible values each type. can automatically types
instance (enum a, bounded a) => serial series _ = [minbound .. maxbound]
this simple way of testing properties exhaustively—literally test every single possible input! there @ least 2 major pitfalls, though: (1) infinite data types lead infinite loops when testing , (2) nested types lead exponentially larger spaces of examples through. in both cases, smallcheck gets large quickly.
so that's point of depth
parameter—it lets system ask keep our series
small. documentation, depth
the
maximum depth of generated test values
for data values, depth of nested constructor applications.
for functional values, both depth of nested case analysis , depth of results.
so let's rework our examples keep them small.
instance serial bool series 0 = [] series 1 = [false] series _ = [false, true] instance serial char series d = take d "abcdefghijklmnopqrstuvwxyz" instance serial => serial (maybe a) -- shrink d 1 since we're adding nothing series d = nothing : map (series (d-1)) instance (enum a, bounded a) => serial series d = take d [minbound .. maxbound]
much better.
so what's coseries
? coarbitrary
in arbitrary
typeclass of quickcheck, lets build series of "small" functions. note we're writing instance on input type---the result type handed in serial
argument (that i'm below calling results
).
instance serial bool coseries results d = [\cond -> if cond r1 else r2 | r1 <- results d r2 <- results d]
these take little more ingenuity write , i'll refer use alts
methods i'll describe briefly below.
so how can make series
of person
s? part easy
instance series person series d = snowwhite : take (d-1) (map dwarf [1..7]) ...
but our coseries
function needs generate every possible function person
s else. can done using altsn
series of functions provided smallcheck. here's 1 way write it
coseries results d = [\person -> case person of snowwhite -> f 0 dwarf n -> f n | f <- alts1 results d ]
the basic idea altsn results
generates series
of n
-ary function n
values serial
instances serial
instance of results
. use create function [0..7], defined serial
value, whatever need, map our person
s numbers , pass 'em in.
so have serial
instance person
, can use build more complex nested serial
instances. "instance", if fairytale
list of person
s, can use serial => serial [a]
instance alongside our serial person
instance create serial fairytale
:
instance serial fairytale series = map makefairytale . series coseries results = map (makefairytale .) . coseries results
(the (makefairytale .)
composes makefairytale
each function coseries
generates, little confusing)
Comments
Post a Comment