12

What is the best practice to display reasons for a failed property test when it is tested via QuickCheck?

Consider for example:

prop a b = res /= []
   where
      (res, reason) = checkCode a b

Then the a session could look like:

> quickCheck prop
Falsifiable, after 48 tests:
42
23

But for debugging it would be really convenient to show the reason for failure as part of the quickCheck falsifable report.

I have hacked it like this:

prop a b = if res /= [] then traceShow reason False else True
   where
      (res, reason) = checkCode a b

Is there is a better/nicer or more quickcheckish way to do it?

maxschlepzig
  • 35,645
  • 14
  • 145
  • 182

4 Answers4

10

I assume that your "reason" variable contains some kind of test-specific data on what went wrong. You could instead return a "Result", which contains both success/fail/invalid conditions and a string explaining what went wrong. Properties that return Results are handled by QuickCheck in exactly the same way as properties that return Bool.

(edit) Like this:

module QtTest where 

import Test.QuickCheck
import Test.QuickCheck.Property as P


-- Always return success
prop_one :: Integer -> P.Result
prop_one _ = MkResult (Just True) True "always succeeds" False [] []


-- Always return failure
prop_two :: Integer -> P.Result
prop_two n = MkResult (Just False) True ("always fails: n = " ++ show n) False [] []

Note that it is the "Result" type defined in Test.QuickCheck.Property you want.

There are also some combinators defined in Test.QuickCheck.Property which help you compose the Result rather than calling the constructor directly, such as

prop_three :: Integer -> Property
prop_three n = printTestCase ("always fails: n = " ++ show n) False

I guess it would be better style to use those.

Paul Johnson
  • 17,438
  • 3
  • 42
  • 59
  • Can you give a simple example how to exactly return a Result such that the "reason" variable (assume it is some string or show-able value) is displayed in case of failure? – maxschlepzig Jan 25 '11 at 14:00
  • Thanks for the update. I was too fixated on http://www.cse.chalmers.se/~rjmh/QuickCheck/manual.html and did not look in the up to date and comprehensive module docs http://hackage.haskell.org/packages/archive/QuickCheck/2.4.0.1/doc/html/Test-QuickCheck-Property.html - it seems that `printTestCase` is a recent addition - quickCheck 2.1 does not include it. – maxschlepzig Jan 27 '11 at 15:20
3

This works in the same way as Paul Johnson's answer but is more concise and robust to changes in MkResult:

import Test.QuickCheck.Property (succeeded, failed, reason)

prop a b =
  if res /= []
    then succeeded
    else failed { reason = reason }
   where
      (res, reason) = checkCode a b
Roman Cheplyaka
  • 37,738
  • 7
  • 72
  • 121
  • Thanks, that's very simple and easy to use. Lets me see exactly what went wrong when I have a big setup before testing multiple properties – unhammer Dec 11 '20 at 20:10
2

Because QuickCheck gives you the inputs to the function, and because the code under test is pure (it is, right?), you can just feed those inputs to the function and get the result. This is more flexible, because with those inputs you can also repeatedly test with tweaks to the original function until it's correct.

Edward Z. Yang
  • 26,325
  • 16
  • 80
  • 110
  • 3
    Well, the point of the question is convenience. Successful testing is all about automating as much as possible. Having to open a ghci session and recomputing a potential expensive function does not make sense. I mean, QuickCheck already provides `collect`, `classify` to be able to enrich the output of *non-failed* property tests. Such enrichment is optional and does not decrease the flexibility at all. – maxschlepzig Jan 23 '11 at 12:33
  • 1
    I mean, there's also a practical reason for this: QuickCheck properties can be arbitrarily complex, so it's not necessarily obvious what the "output" of any given test is: you'll need to massage your code into a form that says "hey, here are the intermediate values I'm interested in!" Assuming you are willing to do this rewriting, though, there's no reason why you couldn't provide this capability, though IIRC QuickCheck doesn't have something baked in for this purpose. – Edward Z. Yang Jan 23 '11 at 12:52
1

This is my solution (I use counterexample instead of printTestCase since the later one is deprecated now):

(<?>) :: (Testable p) => p -> String -> Property
(<?>) = flip (Test.QuickCheck.counterexample . ("Extra Info: " ++))
infixl 2 <?>

Usage:

main :: IO ()
main = hspec $ do
  describe "math" $ do
    prop "sum-of-square-le-square-of-sum" $ do
      \(x :: Int) (y :: Int) ->
        x * x + y * y <= (x + y) * (x + y) <?> show (x * x, y * y, x + y)

So when a test case fails, you can see something like:

   *** Failed! Falsifiable, Falsifiable (after 2 tests):
   1
   -1
   Extra Info: (1,1,0)

You can also use <?> together with .&&., .||., === and ==> etc.:

  describe "math" $ do
    prop "sum-of-square-le-square-of-sum" $ do
      \(x :: Int) (y :: Int) ->
        x * x + y * y <= (x + y) * (x + y) <?> show (x * x, y * y, x + y) .||. (1==0) <?> "haha"
luochen1990
  • 3,689
  • 1
  • 22
  • 37