propertyTest runs a Boolean predicate against randomly generated values. It takes a predicate, a RandomIO generator, and a test count. Each run generates one test case; a true result continues with one fewer test, while a false result prints the failing value. Reaching zero prints “Tests successful!” section 9.1.6
Listing 9.12 An action for property testing using custom random value generators
propertyTest :: Show a => (a -> Bool) -> RandomIO a -> Int -> IO ()
propertyTest predicate random n
| n <= 0 = putStrLn "Tests successful!" #1
| otherwise = do
testCase <- runRandomIO random #2
if predicate testCase #3
then propertyTest predicate
random $ n - 1 #3
else putStrLn $ "Test failed on: " <> show testCase #3
The implementation makes the test loop explicit: generate a value, evaluate the property, recurse after success, or report the counterexample after failure. section 9.1.6
Use propertyTest by supplying three things:
- A property expressed as a function from an input value to Bool. section 9.1.6 section 9.2
- A generator whose output matches the inputs the property should cover, such as asciiString for human-readable ASCII strings. section 9.1.6 section 9.2
- A number of random cases to run, such as 100. section 9.2
Checking ROT13 symmetry
ROT13 should return the original string when applied twice. The property \s -> s == rot13 (rot13 s) is tested with asciiString and 100 generated inputs. Because asciiString includes human-readable ASCII characters rather than only letters, the check also probes whether the symmetry remains robust across those characters. section 9.2
A property test can protect a specification during refactoring: if a change breaks the stated property, a generated failing input can expose it. Properties may also test helper functions, such as whether alphabetRot always returns a character contained in its supplied alphabet. Naming property-test functions with a prop prefix can distinguish them from example-based unit tests. section 9.2 section 9.2
Listing 9.13 A property test for the symmetry of the ROT13 function
propRot13Symmetrical :: IO ()
propRot13Symmetrical =
propertyTest
(\s -> s == rot13 (rot13 s)) #1
asciiString #2
100 #3
This complete property test combines a predicate, the asciiString generator, and 100 runs in one IO action. section 9.2
