mirror of
https://github.com/Mercury-Language/mercury.git
synced 2026-04-18 19:03:45 +00:00
Estimated hours taken: 2 The following change was made by Peter Moulder <pmoulder@csse.monash.edu.au>. extras/quickcheck/tutes/*.html: Many HTML fixes. (Previously there were enough problems that it made more sense to read them as plain text files than with a web browser.) The files now pass through weblint (cleanly) and tidy (only `table lacks "summary" attribute' warnings remain). A small number of textual changes, and also a couple of <!-- XXX ... --> comments where I suspect the text should be changed.
264 lines
7.8 KiB
HTML
264 lines
7.8 KiB
HTML
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2//EN">
|
|
<html>
|
|
<head>
|
|
<title>QuickCheck</title>
|
|
</head>
|
|
<body>
|
|
Files:
|
|
<a href="use.m">use.m</a>
|
|
<a href="use1.m">use1.m</a>
|
|
<a href="use11.m">use11.m</a>
|
|
<a href="nrev.m">nrev.m</a>
|
|
<a href="nrev2.m">nrev2.m</a>
|
|
<br>
|
|
<a href="index.html">Back to main</a>
|
|
|
|
<h1>QuickCheck Tutorial 1</h1>
|
|
|
|
<p>
|
|
Quickcheck is a tool that aids the Mercury programmer in formulating and
|
|
testing properties of programs. The programmer must supply invariant
|
|
functions which test certain properties of the programs. Those invariant
|
|
functions can then be automatically tested on random inputs. It is
|
|
possible to alter the test data distribution or define a custom test
|
|
data generator.
|
|
|
|
<p>
|
|
In order to test the validity of a predicate/function via quickcheck,
|
|
you need the following components:
|
|
<dl>
|
|
<dt>qcheck.m <dd>quickcheck module (supplied)
|
|
<dt>rnd.m <dd>required by quickcheck module (supplied)
|
|
<dt>XXX.m <dd>module containing the predicate/function the user is trying to validate
|
|
<dt>YYY.m <dd>main module, which calls <code>qcheck</code>/4.
|
|
</dl>
|
|
|
|
<h2>A Simple Example</h2>
|
|
|
|
<p>Let XXX.m be "nrev.m", which contains nrev/1:
|
|
|
|
<table border=0 width="100%" bgcolor="#eeeee0"><tr><td><pre>
|
|
:- func nrev(list(T)) = list(T).
|
|
:- mode nrev(in) = out is det.
|
|
nrev([]) = [].
|
|
nrev([X|Xs]) = Ys :-
|
|
list__append(nrev(Xs), [X], Ys).
|
|
</pre></tr></table>
|
|
We want to test whether nrev/1 is properly implemented.
|
|
|
|
<p>
|
|
Let YYY.m be "use.m". It must contain a main/2, and an
|
|
invariant function. If nrev/1 is correctly implemented then it
|
|
should satisfy the following rules:
|
|
<pre>
|
|
reverse [x] = [x]
|
|
reverse (xs ++ ys) = reverse(ys) ++ reverse(xs)
|
|
reverse (reverse xs) = xs
|
|
</pre>
|
|
|
|
<p>
|
|
To test whether 2nd rule holds for nrev/1, define the invariant
|
|
function as :
|
|
<pre>
|
|
testing(Xs, Ys) =
|
|
nrev(Xs ++ Ys) `===` (nrev(Ys) ++ nrev(Xs)).
|
|
</pre>
|
|
|
|
<p>
|
|
`===` function returns 'property:[yes]' if the left side equals
|
|
right side, 'property:[no]' otherwise.
|
|
In theory nrev/1 can take a list(T), however in order for
|
|
quickcheck to automatically generate Xs and Ys, the programmer must
|
|
specify a fixed type for which the law is to be tested, eg:
|
|
<pre>
|
|
:- func testing(list(int), list(int)) = property.
|
|
Or
|
|
:- func testing(list(char), list(char)) = property.
|
|
</pre>
|
|
Now define the main/2 as
|
|
<pre>
|
|
main -->
|
|
qcheck(qcheck__f(testing), "sample testing").
|
|
</pre>
|
|
The complete use.m looks like this:
|
|
|
|
<table border=0 width="100%" bgcolor="#eeeee0"><tr><td><pre>
|
|
|
|
:- module use.
|
|
|
|
:- interface.
|
|
|
|
:- use_module io.
|
|
|
|
:- pred main(io__state, io__state).
|
|
:- mode main(di, uo) is det.
|
|
|
|
%-------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module int, list.
|
|
:- import_module qcheck, nrev.
|
|
|
|
%-------------------------------------------------------------------%
|
|
|
|
main -->
|
|
qcheck(qcheck__f(testing), "sample testing").
|
|
|
|
%-------------------------------------------------------------------%
|
|
% Invariant test functions
|
|
%-------------------------------------------------------------------%
|
|
|
|
:- func testing(list(int), list(int)) = property.
|
|
testing(Xs, Ys) =
|
|
nrev(Xs ++ Ys) `===` (nrev(Ys) ++ nrev(Xs)).
|
|
</pre></tr></table>
|
|
To compile the program, do "mmake use.depend", then "mmake use".
|
|
After running, the statistics should be displayed like :
|
|
<pre>
|
|
Test Description : sample testing
|
|
Number of test cases that succeeded : 100
|
|
Number of trivial tests : 0
|
|
Number of tests cases which failed the pre-condition : 0
|
|
Distributions of selected argument(s) :
|
|
</pre>
|
|
nrev/1 passed 100 tests and failed none (ignore the last 3 lines
|
|
for now).
|
|
|
|
<p>
|
|
If the invariant function is wrongly specified, or nrev/1 is not
|
|
properly implemented, then the statistics may look like :
|
|
<pre>
|
|
Test description : sample testing
|
|
Falsifiable :
|
|
[2]
|
|
[-2, 1]
|
|
</pre>
|
|
where running [2] as Xs and [-2, 1] as Ys failed the invariant function.
|
|
|
|
<p>
|
|
Defining an invariant function which contains only functions is more
|
|
concise than one which contains predicates. Suppose that "nrev2.m"
|
|
defines a predicate version of reversing list:
|
|
|
|
<table border=0 width="100%" bgcolor="#eeeee0"><tr><td><pre>
|
|
:- pred nrev2(list(T), list(T)).
|
|
:- mode nrev2(in, out) is det.
|
|
nrev2([], []).
|
|
nrev2([X|Xs], Ys):-
|
|
nrev2(Xs, Reversed),
|
|
list__append(Reversed, [X], Ys).
|
|
</pre></tr></table>
|
|
Then the invariant function looks like :
|
|
<pre>
|
|
testing2(Xs, Ys) = (Left `===` Right) :-
|
|
nrev2((Xs ++ Ys), Left),
|
|
nrev2(Ys, Part_a),
|
|
nrev2(Ys, Part_b),
|
|
Right = Part_a ++ Part_b.
|
|
</pre>
|
|
Above code is effectively the same as testing/2, however it requires a few extra
|
|
lines to extract the intermediate values, (use1.m):
|
|
|
|
<table border=0 width="100%" bgcolor="#eeeee0"><tr><td><pre>
|
|
:- module use1.
|
|
|
|
:- interface.
|
|
|
|
:- use_module io.
|
|
|
|
:- pred main(io__state, io__state).
|
|
:- mode main(di, uo) is det.
|
|
|
|
%-------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module int, list.
|
|
:- import_module qcheck, nrev2.
|
|
|
|
%-------------------------------------------------------------------%
|
|
|
|
main -->
|
|
qcheck(qcheck__f(testing2), "testing2").
|
|
|
|
%-------------------------------------------------------------------%
|
|
% Invariant test functions
|
|
%-------------------------------------------------------------------%
|
|
|
|
:- func testing2(list(int), list(int)) = property.
|
|
testing2(Xs, Ys) = (Left `===` Right) :-
|
|
nrev2((Xs ++ Ys), Left),
|
|
nrev2(Ys, Part_a),
|
|
nrev2(Xs, Part_b),
|
|
Right = Part_a ++ Part_b.
|
|
</pre></tr></table>
|
|
An alternative to writing a separate invariant function is to
|
|
use Mercury's syntax for higher order terms. When doing this,
|
|
the <code>`with_type`</code> operator can be useful to provide fixed types
|
|
for the arguments of the invariant function. For example (use11.m):
|
|
|
|
<table border=0 width="100%" bgcolor="#eeeee0"><tr><td><pre>
|
|
:- module use11.
|
|
|
|
:- interface.
|
|
|
|
:- use_module io.
|
|
|
|
:- pred main(io__state, io__state).
|
|
:- mode main(di, uo) is det.
|
|
|
|
%--------------------------------------------------------------------------%
|
|
|
|
:- implementation.
|
|
|
|
:- import_module int, list.
|
|
:- import_module qcheck, nrev.
|
|
|
|
%--------------------------------------------------------------------------%
|
|
|
|
main -->
|
|
qcheck(qcheck__f(func(Xs `with_type` list(int), Ys `with_type` list(int))
|
|
= nrev(Xs ++ Ys) `===` (nrev(Ys) ++ nrev(Xs))),
|
|
"sample testing").
|
|
</pre></tr></table>
|
|
|
|
<p>
|
|
In YYY.m, the main/2 should be defined as :
|
|
<pre>
|
|
main -->
|
|
qcheck(qcheck__f(Invariant_Function_X), Test_Desc_Y).
|
|
</pre>
|
|
Where Invariant_Function_X is a higher order term (function) and
|
|
Test_Desc_Y is a string which describe/name/comment Invariant_Function_X.
|
|
|
|
<p>
|
|
The invariant function is of the form
|
|
<pre>
|
|
:- mode Invariant_Function_X(in, in, in ...) = out.
|
|
</pre>
|
|
The invariant function can only take 0 to 10 arguments, but the inputs can
|
|
be of any type. All invariant functions must return a property, which is just
|
|
a list of flags.
|
|
|
|
<table border=0 width="100%" bgcolor="#eeeee0"><tr><td><pre>
|
|
:- type property == list(flag).
|
|
|
|
:- type flag
|
|
---> yes
|
|
; no
|
|
; trivial
|
|
; info(univ)
|
|
; condition.
|
|
</pre></tr></table>
|
|
|
|
<p>QuickCheck does not care what happens inside Invariant_Function_X. If
|
|
the output does not contain 'flag:no', the invariant function is assumed to
|
|
pass the testcase satisfactory.
|
|
|
|
<p>See <a href="T4.html">part 4 of the tutorial</a> to get the details
|
|
on each individual flag.
|
|
|
|
</body>
|
|
</html>
|