-
Notifications
You must be signed in to change notification settings - Fork 158
/
Copy pathQuickStart.fsx
166 lines (112 loc) · 6.64 KB
/
QuickStart.fsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
(*** hide ***)
#I @"../src/FsCheck/bin/Release/netstandard2.0"
#r @"../src/FsCheck.Xunit/bin/Release/netstandard2.0/FsCheck.Xunit.dll"
#r @"../packages/xunit.abstractions/lib/netstandard1.0/xunit.abstractions.dll"
#r @"../packages/xunit.extensibility.core/lib/netstandard1.1/xunit.core.dll"
#r @"../packages/xunit.extensibility.execution/lib/netstandard1.1/xunit.execution.dotnet.dll"
#r "FsCheck"
(**
# Quick Start
The fastest way to understand how FsCheck works is by writing some *properties* - FsCheck's terminology for a parametrized
test, or a generative test - and run them using the built-in test runner. Later on, we'll describe how they can be integrated
with existing test frameworks like NUnit, xUnit.NET or MsTest.
First install FsCheck, open an fsx file and start with:*)
#r "nuget:FsCheck"
open FsCheck
(** In C#: To easily experiment, start a new console app to execute the snippets below (the output is written to console
by default). Alternatively, in LinqPad, reference FsCheck.dll and FSharp.Core.dll, open namespace FsCheck, change the language to "C# statements"
and you should be able to execute most of the snippets as well.
## A Simple Example
A simple example of a property - a test with parameters - is written as a normal F# function that returns a bool: *)
let revRevIsOrig (xs:list<int>) = List.rev(List.rev xs) = xs
(** This property asserts that the reverse of the reverse of a list is the list itself.
To check the property, we load this definition in F# interactive and then invoke *)
(*** define-output: revRevIsOrig ***)
Check.Quick revRevIsOrig
(*** include-output: revRevIsOrig ***)
(** In C#:
[lang=csharp,file=../examples/CSharp.DocSnippets/QuickStart.cs,key=revRevIsOrig]
When a property fails, FsCheck displays a counter-example. For example, if we define *)
(*** define-output: revIsOrig ***)
let revIsOrig (xs:list<int>) = List.rev xs = xs
Check.Quick revIsOrig
(*** include-output: revIsOrig ***)
(** In C#:
[lang=csharp,file=../examples/CSharp.DocSnippets/QuickStart.cs,key=revIsOrig]
FsCheck also *shrinks* the counter example: it tries to find the minimal counter example that
still fails the property. The counter example is indeed minimal:
the list must have at least two different elements for the test to fail. FsCheck displays how many times it
found a smaller (in some way) counter example and so proceeded to shrink further.
To learn more on how to write properties, see [Properties](Properties.html).
## What do I do if a test loops or encounters an error?
In this case we know that the property does not hold, but Check.Quick does not display the counter-example.
There is another testing function provided for this situation. Repeat the test using
<pre>Check.Verbose</pre> or in C# <pre>VerboseCheck()</pre>
which displays each test case before running the test: the last test case displayed is thus
the one in which the loop or error arises.
To learn more on how to run FsCheck tests see [Running Tests](RunningTests.html).
## FsCheck teaches us a lesson
The property above (the reverse of the reverse of a list is the list itself) is not always correct.
Consider a list of floats that contains `NaN` (not a number). Since `NaN <> NaN`, the reverse of
the reverse of `[NaN]` is not actually equal to `[NaN]`. FsCheck has a knack for finding this kind of specification problem.
To see this error, ask FsCheck to generate lists of floats:*)
(***define-output:revFloat***)
let revRevIsOrigFloat (xs:list<float>) = List.rev(List.rev xs) = xs
Check.Quick revRevIsOrigFloat
(***include-output:revFloat***)
(** That said, the example in C# using floats actually works!
[lang=csharp,file=../examples/CSharp.DocSnippets/QuickStart.cs,key=revRevIsOrigFloat]
This is because SequenceEquals uses the default equality comparer under the hood, which uses `Double`'s `Equals` method, which
has a special provision for `NaN`, as you can see in the [reference source](http://referencesource.microsoft.com/#mscorlib/system/double.cs,152).
If we pass an `EqualityComparer` that uses `==` to `SequenceEquals`, the C# example also fails.
As this seemingly trivial example shows, FsCheck helps you discover interesting properties of your code - and so ultimately,
more bugs!
## Using FsCheck with other testing frameworks
Once you have finished your initial exploration of FsCheck, you'll probably want to use it with your existing
unit test framework to augment unit tests or just to run the properties more easily. Below, we'll show some
integration, but we leave it up to you to choose whichever suits your environment best.
### Integration with Expecto
Some testing frameworks like Expecto have an out-of-the-box integration with FsCheck. By using one of the runners that
support FsCheck out of the box, you gain access the the frameworks' reporting capabilities and integrations with IDEs
and build tooling.
Here's a sample:
```fsharp
open Expecto
open Expecto.ExpectoFsCheck
let config = { FsCheck.Config.Default with MaxTest = 10000 }
let properties =
testList "FsCheck samples" [
testProperty "Addition is commutative" <| fun a b ->
a + b = b + a
testProperty "Reverse of reverse of a list is the original list" <|
fun (xs:list<int>) -> List.rev (List.rev xs) = xs
// you can also override the FsCheck config
testPropertyWithConfig config "Product is distributive over addition" <|
fun a b c ->
a * (b + c) = a * b + a * c
]
Tests.runTests defaultConfig properties
```
### Integration with xUnit
Another frequently used runner is xUnit.NET. Here is how to write
the unit test above so it can be run from xUnit.NET:*)
open global.Xunit
[<Fact>]
let ``Reverse of reverse of a list is the original list``() =
let revRevIsOrig (xs:list<int>) = List.rev(List.rev xs) = xs
Check.QuickThrowOnFailure revRevIsOrig
(**
[lang=csharp,file=../examples/CSharp.DocSnippets/QuickStart.cs,key=revRevIsOrigFact]
For xUnit, the test looks like any normal test, and the QuickThrowOnFailure ensures that if the test fails,
an exception with the necessary information is raised so xUnit knows the test failed. The output of the test is the same
as above.
### Using FsCheck with xUnit.NET using the plugin
xUnit.NET is "blessed" with an FsCheck plugin. To use it, install the FsCheck.Xunit NuGet package. The test above can now
be written more tersely as follows:*)
open FsCheck.Xunit
[<Property>]
let ``Reverse of reverse of a list is the original list ``(xs:list<int>) =
List.rev(List.rev xs) = xs
(** xUnit now shows the test similarly to a regular test, and is able to run it directly.
To learn more on how to use this integration or integration with other frameworks like NUnit,
see [Running Tests](RunningTests.html). *)