Files
mercury/extras/quickcheck/tutes/T1.html
David Overton 07038626c1 The following change was made by Peter Moulder <pmoulder@csse.monash.edu.au>.
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.
2002-02-24 23:28:49 +00:00

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 --&gt;
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 --&gt;
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 --&gt;
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 --&gt;
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 --&gt;
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
---&gt; 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>