Skip to content

Commit 0696434

Browse files
committed
Add Guide from purescript/documentation repo
1 parent af9203c commit 0696434

File tree

2 files changed

+223
-1
lines changed

2 files changed

+223
-1
lines changed

GUIDE.md

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
---
2+
title: Test your JavaScript with QuickCheck
3+
author: Phil Freeman
4+
published: 2015-07-16
5+
---
6+
7+
[QuickCheck](http://en.wikipedia.org/wiki/QuickCheck) is a _property-based_ testing library which was originally written in Haskell, but which has been ported to [a](https://github.com/mcandre/node-quickcheck) [number](https://github.com/mcandre/qc) [of](https://github.com/mcandre/cl-quickcheck) [other](https://pypi.python.org/pypi/pytest-quickcheck/) [languages](https://github.com/hayeah/rantly).
8+
9+
QuickCheck works by generating random data with which to test your properties. This allows us to gain confidence that the properties hold, as more and more randomly-generated tests are run.
10+
11+
[`purescript-quickcheck`](http://github.com/purescript/purescript-quickcheck) is a port of QuickCheck to PureScript, which preserves the syntax and types of the original Haskell code.
12+
13+
`purescript-quickcheck` can be used to test code which is written in PureScript, but can be used to test Javascript functions as well. In this post, I'll demonstrate each of these use cases.
14+
15+
#### Testing PureScript Functions
16+
17+
QuickCheck defines the `quickCheck` function, which will print test results to the console, or fail with an exception in the event of a test failure.
18+
19+
Create a new project using Pulp, install `purescript-quickcheck`, and open PSCi:
20+
21+
```text
22+
pulp init
23+
bower install purescript-quickcheck
24+
pulp repl
25+
```
26+
27+
Start by importing the QuickCheck library:
28+
29+
```
30+
> import Prelude
31+
> import Test.QuickCheck
32+
```
33+
34+
The `quickCheck` function takes one argument: the property you would like to test. Let's try a very simple property:
35+
36+
```
37+
> quickCheck \n -> n + 1 == 1 + n
38+
```
39+
40+
If everything worked, you should see the following result:
41+
42+
```
43+
100/100 test(s) passed.
44+
unit
45+
```
46+
47+
This indicates 100 successful random test runs.
48+
49+
#### Error Messages
50+
51+
Let's see what happens when we try testing a broken property:
52+
53+
```
54+
> quickCheck \n -> n + 1 == n
55+
```
56+
57+
You should see an exception printed to the console:
58+
59+
```
60+
Error: Test 1 failed:
61+
Failed: Test returned false
62+
```
63+
64+
That's not a very helpful error, so let's improve it:
65+
66+
```
67+
> quickCheck \n -> n + 1 == n <?> "Test failed for input " <> show n
68+
```
69+
70+
This time you should see the following failure message:
71+
72+
```
73+
Error: Test 1 failed:
74+
Test failed for input -654791
75+
```
76+
77+
Alternatively, we could use the `===` operator, which provides a better error message:
78+
79+
```
80+
> quickCheck \n -> n + 1 === n
81+
Error: Test 1 failed:
82+
-663820 /= -663821
83+
```
84+
85+
#### Example 1 - GCD Function
86+
87+
Let's write an implementation of the _greatest common divisor_ function in PSCi.
88+
89+
This function is easiest to enter in the multi-line input mode, so switch to
90+
that by entering `:paste` at the prompt:
91+
92+
```
93+
> :paste
94+
… gcd 0 n = n
95+
… gcd n 0 = n
96+
… gcd n m | n < 0 = gcd (-n) m
97+
… gcd n m | m < 0 = gcd n (-m)
98+
… gcd n m | n > m = gcd (n - m) m
99+
… gcd n m = gcd n (m - n)
100+
101+
```
102+
103+
After typing that in, press Control-D to finish the input.
104+
105+
Now let's assert some basic properties that we expect to hold of the `gcd` function.
106+
107+
```
108+
> quickCheck \n -> gcd n 1 === 1
109+
```
110+
111+
This test should pass, but might take a while, because the standard random generator for integers which comes bundled with `purescript-quickcheck` generates integers in the range -1000000 to 1000000.
112+
113+
We can modify our test to only consider small integers:
114+
115+
```
116+
> quickCheck \n -> gcd (n / 1000) 1 === 1
117+
```
118+
119+
This time, the test should complete quickly. However, we've coupled the generation of our data (`/ 1000`) with the property we're testing, which is against the spirit of QuickCheck. A better approach is to define a `newtype` which can be used to generate small integers.
120+
121+
Create a new file `src/SmallInt.purs` and paste the following code:
122+
123+
```
124+
module SmallInt where
125+
126+
import Prelude
127+
128+
import Test.QuickCheck
129+
import Test.QuickCheck.Arbitrary
130+
131+
data SmallInt = SmallInt Int
132+
133+
runInt :: SmallInt -> Int
134+
runInt (SmallInt i) = i
135+
136+
instance arbSmallInt :: Arbitrary SmallInt where
137+
arbitrary = map (SmallInt <<< (_ / 1000)) arbitrary
138+
```
139+
140+
Back in PSCi, we can now test properties without having to explicitly define how to generate our random data:
141+
142+
```
143+
> quickCheck \(SmallInt n) (SmallInt m) -> gcd n m == gcd m n
144+
```
145+
146+
The idea is that the particular scheme that is chosen to generate data should be indicated by the types of our function arguments, so `newtype`s can be quite useful when defining multiple data generation schemes for a single type.
147+
148+
#### Example 2 - Testing Higher Order Functions
149+
150+
QuickCheck can also be used to test higher-order functions, by randomly generating functions.
151+
152+
Let's test that the `map` function on arrays satisfies the functor laws.
153+
154+
For these two tests, I will write the test function using a let binding to avoid having to write type signatures in properties.
155+
156+
The first functor law says that if you map a function which does not modify its argument (the identity function) over a structure, then the structure should not be modified either.
157+
158+
```
159+
> import Data.Array
160+
> :paste
161+
… firstFunctorLaw :: Array Int -> Boolean
162+
… firstFunctorLaw arr = map id arr == arr
163+
164+
> quickCheck firstFunctorLaw
165+
100/100 test(s) passed.
166+
unit
167+
```
168+
169+
The second functor law says that mapping two functions over a structure one-by-one is equivalent to mapping their composition over the structure:
170+
171+
```
172+
> :paste
173+
… secondFunctorLaw :: (Int -> Int) -> (Int -> Int) -> Array Int -> Boolean
174+
… secondFunctorLaw f g arr = map f (map g arr) == map (f <<< g) arr
175+
176+
> quickCheck secondFunctorLaw
177+
100/100 test(s) passed.
178+
unit
179+
```
180+
181+
#### Testing Javascript Functions
182+
183+
Now let's try an example of testing a function written in Javascript.
184+
185+
[This file](https://gist.github.com/paf31/3aedd6c3e3ac5c8a78e7) contains a set of FFI bindings for some of the functions defined by the [UnderscoreJS](http://underscorejs.org/) library. It is a nice example of a set of pure functions written in Javascript which we can test with QuickCheck.
186+
187+
Copy the contents of that file into `src/UnderscoreFFI.purs`, and reload PSCi with that module loaded:
188+
189+
```
190+
> import UnderscoreFFI
191+
```
192+
193+
The `UnderscoreFFI` module defines a wrapper for the `sortBy` function. Let's test that the function is idempotent:
194+
195+
```
196+
> :paste
197+
… sortIsIdempotent :: Array Int -> Boolean
198+
… sortIsIdempotent arr = sortBy id (sortBy id arr) == sortBy id arr
199+
200+
> quickCheck sortIsIdempotent
201+
100/100 test(s) passed.
202+
unit
203+
```
204+
205+
In fact, we don't need to sort by the identity function. Since QuickCheck supports higher-order functions, we can test with a randomly-generated sorting function:
206+
207+
```
208+
> :paste
209+
… sortIsIdempotent' :: (Int -> Int) -> Array Int -> Boolean
210+
… sortIsIdempotent' f arr = sortBy f (sortBy f arr) == sortBy f arr
211+
212+
> quickCheck sortIsIdempotent
213+
100/100 test(s) passed.
214+
unit
215+
```
216+
217+
Have a look through the `UnderscoreFFI` module, and see what other properties you can define.
218+
219+
#### Conclusion
220+
221+
Hopefully I've shown that QuickCheck can be a useful tool, whether you write your code in PureScript or not. Its strength is in its _type-directed_ approach to data generation, which allows you to say _what_ you want to test directly, rather than _how_ to generate test data.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ bower install purescript-quickcheck
1313

1414
## Documentation
1515

16-
Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-quickcheck).
16+
* [Guide](GUIDE.md)
17+
* Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-quickcheck).

0 commit comments

Comments
 (0)