I'm new to Haskell. It's very nice so far, but I'm running into copy-pasting for my QuickCheck properties, and I'd like to fix that.
Here's a made-up example:
prop_Myfunc :: [Int] -> (Int,Int) -> Bool
prop_Myfunc ints (i,j) = ints !! i == ints !! j
This won't work because QuickCheck generates negative numbers, so I get
*** Failed! (after 2 tests and 2 shrinks):
Exception:
Prelude.(!!): negative index
I've tried to google for solutions to this, and I've found e.g. NonNegative and ==>, but I don't understand how they work.
How can I restrict the above example so that i and j are never negative? And also, so that neither is too high? That is: 0 <= i,j < length ints
First, see this SO answer for an example of how to write a custom Gen ...
function and how to use the forAll
combinator.
And here is how to write a generator for a non-empty list and two valid non-negative indices into the list:
import Test.QuickCheck
genArgs :: Gen ( [Int], Int, Int )
genArgs = do
x <- arbitrary
xs <- arbitrary
let n = length xs
i <- choose (0,n)
j <- choose (0,n)
return ( (x:xs), i, j) -- return a non-empty list
test = quickCheck $ forAll genArgs $ \(xs,i,j) -> prop_myfunc xs (i,j)
Constraining wrappers (from Test.QuickCheck.Modifiers
, if they aren't reexported implicitly) can be used in this way:
prop_Myfunc :: [Int] -> (NonNegative Int, NonNegative Int) -> Bool
prop_Myfunc ints (NonNegative i, NonNegative j) = ints !! i == ints !! j
You can treat SomeWrapper a
as a
with modified distribution. For example, NonNegative a
ensures that a >= 0
. After the wrapper was generated, the value can be get with pattern-matching or explicit accessor (getNonNegative
in this case).
As for constraining the top margin of your indices, I think it's not possible with wrappers (it's impossible in the Haskkell type system to parameterise a type with the value, the list length in this case). However, with the ==>
operator you can add an arbitrary boolean constraint for your test:
prop_Myfunc ints (NonNegative i, NonNegative j) = i < l && j < l ==> ints !! i == ints !! j where
l = length ints
It works in other way: when the condition isn't true, it simply discards the current test case. But be careful: if there are too many thrown cases (the condition is too restrictive), the test becomes much less useful. A „lossless“ behaviour can be often achieved with shrink
ing test data, but it's a whole other topic.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With