-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathscott_wlaschin.tex
1858 lines (1549 loc) · 65.1 KB
/
scott_wlaschin.tex
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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
\chapter{Choosing properties for property-based testing - Scott Wlaschin}
\label{sec:choosing_properties_for_testing}
\begin{quotation}
\noindent\textit{\textbf{William Yao:}}
\textit{More starting points for figuring out what properties to test.}
\vspace{\baselineskip}
\noindent\textit{Original article: \cite{choosing_properties}}
\end{quotation}
\noindent\textit{UPDATE: I did a talk on property-based testing based on these posts. \href{https://fsharpforfunandprofit.com/pbt/}{Slides and video here}.}
\vspace{\baselineskip}
\noindent In the \href{https://fsharpforfunandprofit.com/posts/property-based-testing/}{previous post}, I described the basics of property-based testing, and showed how it could save a lot of time by generating random tests.
But here's a common problem. Everyone who sees a property-based testing tool like FsCheck or QuickCheck thinks that it is amazing\ldots but when it times come to start creating your own properties, the universal complaint is: ``what properties should I use? I can't think of any!''
The goal of this post is to show some common patterns that can help you discover the properties that are applicable to your code.
\section{Categories for properties}
In my experience, many properties can be discovered by using one of the seven approaches listed below.
\begin{itemize}
\item ``Different paths, same destination'' (see \ref{sec:different_paths_same_destination})
\item ``There and back again'' (see \ref{sec:there_and_back_again})
\item ``Some things never change'' (see \ref{sec:some_things_never_change})
\item ``The more things change, the more they stay the same'' (see \ref{sec:the_more_things_change})
\item ``Solve a smaller problem first'' (see \ref{sec:solve_a_smaller_problem_first})
\item ``Hard to prove, easy to verify'' (see \ref{sec:hard_to_prove})
\item ``The test oracle'' (see \ref{sec:the_test_oracle})
\end{itemize}
This is by no means a comprehensive list, just the ones that have been most useful to me. For a different perspective, check out the list of patterns that the PEX team at Microsoft have compiled.
\subsection{``Different paths, same destination''}
\label{sec:different_paths_same_destination}
These kinds of properties are based on combining operations in different orders, but getting the same result. For example, in the diagram below, doing $X$ then $Y$ gives the same result as doing $Y$ followed by $X$ (see figure \ref{fig:choosing_properties_1}).
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_1.png}
\caption{Commutative Properties}
\label{fig:choosing_properties_1}
\end{figure}
The commutative property of addition is an obvious example of this pattern. For example, the result of add 1 then add 2 is the same as the result of add 2 followed by add 1.
This pattern, generalized, can produce a wide range of useful properties. We'll see some more uses of this pattern later in this post.
\subsection{``There and back again''}
\label{sec:there_and_back_again}
These kinds of properties are based on combining an operation with its inverse, ending up with the same value you started with.
In the diagram below, doing $X$ serializes ABC to some kind of binary format, and the inverse of $X$ is some sort of deserialization that returns the same ABC value again (see figure \ref{fig:choosing_properties_2}).
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_2.png}
\caption{Inverse Properties}
\label{fig:choosing_properties_2}
\end{figure}
In addition to serialization/deserialization, other pairs of operations can be checked this way: \texttt{addition}/\texttt{subtraction}, \texttt{write}/\texttt{read}, \texttt{setProperty}/\texttt{getProperty}, and so on.
Other pair of functions fit this pattern too, even though they are not strict inverses, pairs such as \texttt{insert}/\texttt{contains}, \texttt{create}/\texttt{exists} , etc.
\subsection{``Some things never change''}
\label{sec:some_things_never_change}
These kinds of properties are based on an invariant that is preserved after some transformation.
In the diagram below (figure \ref{fig:choosing_properties_3}), the transform changes the order of the items, but the same four items are still present afterwards.
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_3.png}
\caption{Invariant Properties}
\label{fig:choosing_properties_3}
\end{figure}
Common invariants include size of a collection (for \texttt{map} say), the contents of a collection (for sort say), the height or depth of something in proportion to size (e.g. balanced trees).
\subsection{``The more things change, the more they stay the same''}
\label{sec:the_more_things_change}
These kinds of properties are based on ``idempotence'' -- that is, doing an operation twice is the same as doing it once.
In the diagram below (figure \ref{fig:choosing_properties_4}), using distinct to filter the set returns two items, but doing distinct twice returns the same set again.
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_4.png}
\caption{Idempotent Properties}
\label{fig:choosing_properties_4}
\end{figure}
Idempotence properties are very useful, and can be extended to things like database updates and message processing.
\subsection{``Solve a smaller problem first''}
\label{sec:solve_a_smaller_problem_first}
These kinds of properties are based on ``structural induction'' -- that is, if a large thing can be broken into smaller parts, and some property is true for these smaller parts, then you can often prove that the property is true for a large thing as well.
In the diagram below (figure \ref{fig:choosing_properties_5}), we can see that the four-item list can be partitioned into an item plus a three-item list, which in turn can be partitioned into an item plus a two-item list. If we can prove the property holds for two-item list, then we can infer that it holds for the three-item list, and for the four-item list as well.
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_5.png}
\caption{Induction Properties}
\label{fig:choosing_properties_5}
\end{figure}
Induction properties are often naturally applicable to recursive structures such as lists and trees.
\subsection{``Hard to prove, easy to verify''}
\label{sec:hard_to_prove}
Often an algorithm to find a result can be complicated, but verifying the answer is easy.
In the diagram below (figure \ref{fig:choosing_properties_6}), we can see that finding a route through a maze is hard, but checking that it works is trivial!
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_6.png}
\caption{Easy to Verify Properties}
\label{fig:choosing_properties_6}
\end{figure}
Many famous problems are of this sort, such as prime number factorization. But this approach can be used for even simple problems.
For example, you might check that a string tokenizer works by just concatenating all the tokens again. The resulting string should be the same as what you started with.
\subsection{``The test oracle''}
\label{sec:the_test_oracle}
In many situations you often have an alternate version of an algorithm or process (a ``test oracle'') that you can use to check your results (figure \ref{fig:choosing_properties_7}).
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_7.png}
\caption{Test Oracle}
\label{fig:choosing_properties_7}
\end{figure}
For example, you might have a high-performance algorithm with optimization tweaks that you want to test. In this case, you might compare it with a brute force algorithm that is much slower but is also much easier to write correctly.
Similarly, you might compare the result of a parallel or concurrent algorithm with the result of a linear, single thread version.
\section{Putting the categories to work with some real examples}
In this section, we'll apply these categories to see if we can come up with properties for some simple functions such as ``sort a list'' and ``reverse a list''.
\subsection{``Different paths, same destination'' applied to a list sort}
Let's start with ``different paths, same destination'' and apply it to a ``list sort'' function.
Can we think of any way of combining an operation \textit{before} \texttt{List.sort}, and another operation \textit{after} \texttt{List.sort}, so that you should end up with the same result? That is, so that ``going up then across the top'' is the same as ``going across the bottom then up''.
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_8.png}
\caption{List Sort Property}
\label{fig:choosing_properties_8}
\end{figure}
How about this?
\begin{itemize}
\item \textbf{Path 1}: We add one to each element of the list, then sort.
\item \textbf{Path 2}: We sort, then add one to each element of the list.
\item Both lists should be equal.
\end{itemize}
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_9.png}
\caption{List Sort Property +1}
\label{fig:choosing_properties_9}
\end{figure}
Here's some code that implements that property:
\begin{minted}{fsharp}
let ``+1 then sort should be same as sort then +1`` sortFn aList =
let add1 x = x + 1
let result1 = aList |> sortFn |> List.map add1
let result2 = aList |> List.map add1 |> sortFn
result1 = result2
// test
let goodSort = List.sort
Check.Quick (``+1 then sort should be same as sort then +1`` goodSort)
// Ok, passed 100 tests.
\end{minted}
Well, that works, but it also would work for a lot of other transformations too. For example, if we implemented \texttt{List.sort} as just the identity, then this property would be satisfied equally well! You can test this for yourself:
\begin{minted}{fsharp}
let badSort aList = aList
Check.Quick (``+1 then sort should be same as sort then +1`` badSort)
// Ok, passed 100 tests.
\end{minted}
The problem with this property is that it is not exploiting any of the ''sortedness``. We know that a sort will probably reorder a list, and certainly, the smallest element should be first.
How about adding an item that we know will come at the front of the list after sorting?
\begin{itemize}
\item \textbf{Path 1}: We append Int32.MinValue to the end of the list, then sort.
\item \textbf{Path 2}: We sort, then prepend Int32.MinValue to the front of the list.
\item Both lists should be equal.
\end{itemize}
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_10.png}
\caption{List Sort Property with \texttt{Int32.MinValue}}
\label{fig:choosing_properties_10}
\end{figure}
Here's the code:
\begin{minted}{fsharp}
let ``append minValue then sort should be same as sort then prepend minValue``
sortFn aList =
let minValue = Int32.MinValue
let appendThenSort = (aList @ [minValue]) |> sortFn
let sortThenPrepend = minValue :: (aList |> sortFn)
appendThenSort = sortThenPrepend
// test
Check.Quick (``append minValue then sort should be same as sort then
prepend minValue`` goodSort)
// Ok, passed 100 tests.
\end{minted}
The bad implementation fails now!
\begin{minted}{fsharp}
Check.Quick (``append minValue then sort should be same as sort then
prepend minValue`` badSort)
// Falsifiable, after 1 test (2 shrinks)
// [0]
\end{minted}
In other words, the bad sort of \texttt{[0; minValue]} is \textit{not} the same as \texttt{[minValue; 0]}.
So that's good!
But\ldots we've got some hard coded things in there that the Enterprise Developer From Hell (\href{https://fsharpforfunandprofit.com/posts/property-based-testing/}{see previous post}) could take advantage of! The EDFH will exploit the fact that we always use \texttt{Int32.MinValue} and that we always prepend or append it to the test list.
In other words, the EDFH can identify which path we are on and have special cases for each one:
\begin{minted}{fsharp}
// The Enterprise Developer From Hell strikes again
let badSort2 aList =
match aList with
| [] -> []
| _ ->
let last::reversedTail = List.rev aList
if (last = Int32.MinValue) then
// if min is last, move to front
let unreversedTail = List.rev reversedTail
last :: unreversedTail
else
aList // leave alone
\end{minted}
And when we check it\ldots
\begin{minted}{fsharp}
// Oh dear, the bad implementation passes!
Check.Quick (``append minValue then sort should be same as sort then
prepend minValue`` badSort2)
// Ok, passed 100 tests.
\end{minted}
We could fix this by (a) picking a random number smaller than any number in the list and (b) inserting it at a random location rather than always appending it. But rather than getting too complicated, let's stop and reconsider.
An alternative approach which also exploits the ''sortedness`` is to first negate all the values, then on the path that negates \textit{after} the sort, add an extra reverse as well.
\begin{figure}[htbp]
\centering
\includegraphics[width=.95\linewidth]{./pics/choosing_properties_11.png}
\caption{List Sort Property with negate}
\label{fig:choosing_properties_11}
\end{figure}
\begin{minted}{fsharp}
let ``negate then sort should be same as sort then negate then reverse``
sortFn aList =
let negate x = x * -1
let negateThenSort = aList |> List.map negate |> sortFn
let sortThenNegateAndReverse = aList |> sortFn |> List.map
negate |> List.rev
negateThenSort = sortThenNegateAndReverse
\end{minted}
This property is harder for the EDFH to beat because there are no magic numbers to help identify which path you are on:
\begin{minted}{fsharp}
// test
Check.Quick ( ``negate then sort should be same as sort then negate then
reverse`` goodSort)
// Ok, passed 100 tests.
// test
Check.Quick ( ``negate then sort should be same as sort then negate then
reverse`` badSort)
// Falsifiable, after 1 test (1 shrinks)
// [1; 0]
// test
Check.Quick ( ``negate then sort should be same as sort then negate then
reverse`` badSort2)
// Falsifiable, after 5 tests (3 shrinks)
// [1; 0]
\end{minted}
You might argue that we are only testing sorting for lists of integers. But the \texttt{List.sort} function is generic and knows nothing about integers per se, so I have high confidence that this property does test the core sorting logic.
\subsubsection{Applying ``different paths, same destination'' to a list
reversal function}
\label{applying-different-paths-same-destination-to-a-list-reversal-function}
Ok, enough of \texttt{List.sort}. What about applying the same ideas to
the list reversal function?
We can do the same append/prepend trick:
\begin{figure}[htbp]
\centering
\includegraphics{pics/property_list_rev.png}
\caption{List reverse}
\end{figure}
Here's the code for the property:
\begin{minted}{fsharp}
let ``append any value then reverse should be same as reverse then prepend
same value`` revFn anyValue aList =
let appendThenReverse = (aList @ [anyValue]) |> revFn
let reverseThenPrepend = anyValue :: (aList |> revFn)
appendThenReverse = reverseThenPrepend
\end{minted}
Here are the test results for the correct function and for two incorrect
functions:
\begin{minted}{fsharp}
// test
let goodReverse = List.rev
Check.Quick (``append any value then reverse should be same as reverse
then prepend same value`` goodReverse)
// Ok, passed 100 tests.
// bad implementation fails
let badReverse aList = []
Check.Quick (``append any value then reverse should be same as reverse
then prepend same value`` badReverse)
// Falsifiable, after 1 test (2 shrinks)
// true, []
// bad implementation fails
let badReverse2 aList = aList
Check.Quick (``append any value then reverse should be same as reverse
then prepend same value`` badReverse2)
// Falsifiable, after 1 test (1 shrinks)
// true, [false]
\end{minted}
You might notice something interesting here. I never specified the type
of the list. The property works with \emph{any} list.
In cases like these, FsCheck will generate random lists of bools,
strings, ints, etc.
In both failing cases, the \texttt{anyValue} is a bool. So FsCheck is
using lists of bools to start with.
Here's an exercise for you: Is this property good enough? Is there some
way that the EDFH can create an implementation that will pass?
\section{``There and back again''}
\label{there-and-back-again2}
Sometimes the multi-path style properties are not available or too
complicated, so let's look at some other approaches.
We'll start with properties involving inverses.
Let's start with list sorting again. Is there an inverse to sorting?
Hmmm, not really. So we'll skip sorting for now.
What about list reversal? Well, as it happens, reversal is its own
inverse!
\begin{figure}[htbp]
\centering
\includegraphics{pics/property_list_rev_inverse.png}
\caption{List reverse with inverse}
\end{figure}
Let's turn that into a property:
\begin{minted}{haskell}
let ``reverse then reverse should be same as original`` revFn aList =
let reverseThenReverse = aList |> revFn |> revFn
reverseThenReverse = aList
\end{minted}
And it passes:
\begin{minted}{fsharp}
let goodReverse = List.rev
Check.Quick (``reverse then reverse should be same as original`` goodReverse)
// Ok, passed 100 tests.
\end{minted}
Unfortunately, a bad implementation satisfies the property too!
\begin{minted}{fsharp}
let badReverse aList = aList
Check.Quick (``reverse then reverse should be same as original`` badReverse)
// Ok, passed 100 tests.
\end{minted}
Nevertheless, the use of properties involving inverses can be very
useful to verify that your inverse function (such as deserialization)
does indeed ``undo'' the primary function (such as serialization).
We'll see some real examples of using this in the next post.
\section{``Hard to prove, easy to
verify''}
\label{hard-to-prove-easy-to-verify2}
So far we've been testing properties without actually caring about the
end result of an operation.
But of course in practice, we do care about the end result!
Now we normally can't really tell if the result is right without
duplicating the function under test. But often we can tell that the
result is \emph{wrong} quite easily. In the maze diagram from above, we
can easily check whether the path works or not.
If we are looking for the \emph{shortest} path, we might not be able to
check it, but at least we know that we have \emph{some} valid path.
This principle can be applied quite generally.
For example, let's say that we want to check whether a
\texttt{string\ split} function is working. We don't have to write a
tokenizer -- all we have to do is ensure that the tokens, when
concatenated, give us back the original string!
\begin{figure}[htbp]
\centering
\includegraphics{pics/property_string_split.png}
\caption{String split property}
\end{figure}
Here's the core code from that property:
\begin{minted}{fsharp}
let concatWithComma s t = s + "," + t
let tokens = originalString.Split [| ',' |]
let recombinedString =
// can use reduce safely because there is always at least one token
tokens |> Array.reduce concatWithComma
// compare the result with the original
originalString = recombinedString
\end{minted}
But how can we create an original string? The random strings generated
by FsCheck are unlikely to contain many commas!
There are ways that you can control exactly how FsCheck generates random
data, which we'll look at later.
For now though, we'll use a trick. The trick is to let FsCheck generate
a list of random strings, and then we'll build an
\texttt{originalString} from them by concatting them together.
So here's the complete code for the property:
\begin{minted}{fsharp}
let ``concatting the elements of a string split by commas recreates the
original string`` aListOfStrings =
// helper to make a string
let addWithComma s t = s + "," + t
let originalString = aListOfStrings |> List.fold addWithComma ""
// now for the property
let tokens = originalString.Split [| ',' |]
let recombinedString =
// can use reduce safely because there is always at least
// one token
tokens |> Array.reduce addWithComma
// compare the result with the original
originalString = recombinedString
\end{minted}
When we test this we are happy:
\begin{minted}{fsharp}
Check.Quick ``concatting the elements of a string split by commas recreates
the original string``
// Ok, passed 100 tests.
\end{minted}
\subsubsection{``Hard to prove, easy to verify'' for list
sorting}
\label{hard-to-prove-easy-to-verify-for-list-sorting}
So how can we apply this principle to a sorted list? What property is
easy to verify?
The first thing that pops into my mind is that for each pair of elements
in the list, the first one will be smaller than the second.
\begin{figure}[htbp]
\centering
\includegraphics{pics/property_list_sort_pairwise.png}
\caption{Pairwise property}
\end{figure}
So let's make that into a property:
\begin{minted}{fsharp}
let ``adjacent pairs from a list should be ordered`` sortFn aList =
let pairs = aList |> sortFn |> Seq.pairwise
pairs |> Seq.forall (fun (x,y) -> x <= y )
\end{minted}
But something funny happens when we try to check it. We get an error!
\begin{minted}{fsharp}
let goodSort = List.sort
Check.Quick (``adjacent pairs from a list should be ordered`` goodSort)
\end{minted}
\begin{verbatim}
System.Exception: Geneflect: type not handled System.IComparable
at [email protected](String message)
at Microsoft.FSharp.Core.PrintfImpl.go@523-3[b,c,d](String fmt,
Int32 len, FSharpFunc`2 outputChar, FSharpFunc`2 outa, b os,
FSharpFunc`2 finalize, FSharpList`1 args, Int32 i)
at Microsoft.FSharp.Core.PrintfImpl.run@521[b,c,d](FSharpFunc`2
initialize, String fmt, Int32 len, FSharpList`1 args)
\end{verbatim}
What does
\texttt{System.Exception:\ type\ not\ handled\ System.IComparable} mean?
It means that FsCheck is trying to generate a random list, but all it
knows is that the elements must be \texttt{IComparable}. But
\texttt{IComparable} is not a type than can be instantiated, so FsCheck
throws an error.
How can we prevent this from happening? The solution is to specify a
particular type for the property, such as \texttt{int\ list}, like this:
\begin{minted}{fsharp}
let ``adjacent pairs from a list should be ordered`` sortFn
(aList:int list) =
let pairs = aList |> sortFn |> Seq.pairwise
pairs |> Seq.forall (fun (x,y) -> x <= y )
\end{minted}
This code works now.
\begin{minted}{fsharp}
let goodSort = List.sort
Check.Quick (``adjacent pairs from a list should be ordered`` goodSort)
// Ok, passed 100 tests.
\end{minted}
Note that even though the property has been constrained, the property is
still a very general one. We could have used \texttt{string\ list}
instead, for example, and it would work just the same.
\begin{minted}{fsharp}
let ``adjacent pairs from a string list should be ordered`` sortFn
(aList:string list) =
let pairs = aList |> sortFn |> Seq.pairwise
pairs |> Seq.forall (fun (x,y) -> x <= y )
Check.Quick (``adjacent pairs from a string list should be ordered`` goodSort)
// Ok, passed 100 tests.
\end{minted}
\textbf{TIP: If FsCheck throws ``type not handled'', add explicit type
constraints to your property}
Are we done now? No! One problem with this property is that it doesn't
catch malicious implementations by the EDFH.
\begin{minted}{fsharp}
// bad implementation passes
let badSort aList = []
Check.Quick (``adjacent pairs from a list should be ordered`` badSort)
// Ok, passed 100 tests.
\end{minted}
Is it a surprise to you that a silly implementation also works?
Hmmm. That tells us that there must be some property \emph{other than
pairwise ordering} associated with sorting that we've overlooked. What
are we missing here?
This is a good example of how doing property-based testing can lead to
insights about design. We thought we knew what sorting meant, but we're
being forced to be a bit stricter in our definition.
As it happens, we'll fix this particular problem by using the next
principle!
\section{``Some things never
change''}
\label{some-things-never-change2}
A useful kind of property is based on an invariant that is preserved
after some transformation, such as preserving length or contents.
They are not normally sufficient in themselves to ensure a correct
implementation, but they \emph{do} often act as a counter-check to more
general properties.
For example, in \href{https://fsharpforfunandprofit.com/posts/property-based-testing/}{the previous
post}, we created commutative and associative properties for addition,
but then noticed that simply having an implementation that returned zero
would satisfy them just as well! It was only when we added
\texttt{x\ +\ 0\ =\ x} as a property that we could eliminate that
particular malicious implementation.
And in the ``list sort'' example above, we could satisfy the ``pairwise
ordered'' property with a function that just returned an empty list! How
could we fix that?
Our first attempt might be to check the length of the sorted list. If
the lengths are different, then the sort function obviously cheated!
\begin{minted}{fsharp}
let ``sort should have same length as original`` sortFn (aList:int list) =
let sorted = aList |> sortFn
List.length sorted = List.length aList
\end{minted}
We check it and it works:
\begin{minted}{fsharp}
let goodSort = List.sort
Check.Quick (``sort should have same length as original`` goodSort )
// Ok, passed 100 tests.
\end{minted}
And yes, the bad implementation fails:
\begin{minted}{fsharp}
let badSort aList = []
Check.Quick (``sort should have same length as original`` badSort )
// Falsifiable, after 1 test (1 shrink)
// [0]
\end{minted}
Unfortunately, the BDFH is not defeated and can come up with another
compliant implementation! Just repeat the first element N times!
\begin{minted}{fsharp}
// bad implementation has same length
let badSort2 aList =
match aList with
| [] -> []
| head::_ -> List.replicate (List.length aList) head
// for example
// badSort2 [1;2;3] => [1;1;1]
\end{minted}
Now when we test this, it passes:
\begin{minted}{fsharp}
Check.Quick (``sort should have same length as original`` badSort2)
// Ok, passed 100 tests.
\end{minted}
What's more, it also satisfies the pairwise property too!
\begin{minted}{fsharp}
Check.Quick (``adjacent pairs from a list should be ordered`` badSort2)
// Ok, passed 100 tests.
\end{minted}
\subsection{Sort invariant - 2nd
attempt}
\label{sort-invariant---2nd-attempt}
So now we have to try again. What is the difference between the real
result \texttt{{[}1;2;3{]}} and the fake result \texttt{{[}1;1;1{]}}?
Answer: the fake result is throwing away data. The real result always
contains the same contents as the original list, but just in a different
order.
\begin{figure}[htbp]
\centering
\includegraphics{pics/property_list_sort_permutation.png}
\caption{Permutation property}
\end{figure}
That leads us to a new property: a sorted list is always a permutation
of the original list. Aha! Let's write the property in terms of
permutations now:
\begin{minted}{fsharp}
let ``a sorted list is always a permutation of the original list``
sortFn (aList:int list) =
let sorted = aList |> sortFn
let permutationsOfOriginalList = permutations aList
// the sorted list must be in the seq of permutations
permutationsOfOriginalList
|> Seq.exists (fun permutation -> permutation = sorted)
\end{minted}
Great, now all we need is a permutation function.
Let's head over to StackOverflow and \sout{steal}
\href{http://stackoverflow.com/a/4610704/1136133}{borrow an
implementation}. Here it is:
\begin{minted}{fsharp}
/// given aList and anElement to insert,
/// generate all possible lists with anElement
/// inserted into aList
let rec insertElement anElement aList =
// From http://stackoverflow.com/a/4610704/1136133
seq {
match aList with
// empty returns a singleton
| [] -> yield [anElement]
// not empty?
| first::rest ->
// return anElement prepended to the list
yield anElement::aList
// also return first prepended to all the sublists
for sublist in insertElement anElement rest do
yield first::sublist
}
/// Given a list, return all permutations of it
let rec permutations aList =
seq {
match aList with
| [] -> yield []
| first::rest ->
// for each sub-permutation,
// return the first inserted into it somewhere
for sublist in permutations rest do
yield! insertElement first sublist
}
\end{minted}
Some quick interactive tests confirm that it works as expected:
\begin{minted}{fsharp}
permutations ['a';'b';'c'] |> Seq.toList
// [['a'; 'b'; 'c']; ['b'; 'a'; 'c']; ['b'; 'c'; 'a']; ['a'; 'c'; 'b'];
// ['c'; 'a'; 'b']; ['c'; 'b'; 'a']]
permutations ['a';'b';'c';'d'] |> Seq.toList
// [['a'; 'b'; 'c'; 'd']; ['b'; 'a'; 'c'; 'd']; ['b'; 'c'; 'a'; 'd'];
// ['b'; 'c'; 'd'; 'a']; ['a'; 'c'; 'b'; 'd']; ['c'; 'a'; 'b'; 'd'];
// ['c'; 'b'; 'a'; 'd']; ['c'; 'b'; 'd'; 'a']; ['a'; 'c'; 'd'; 'b'];
// ['c'; 'a'; 'd'; 'b']; ['c'; 'd'; 'a'; 'b']; ['c'; 'd'; 'b'; 'a'];
// ['a'; 'b'; 'd'; 'c']; ['b'; 'a'; 'd'; 'c']; ['b'; 'd'; 'a'; 'c'];
// ['b'; 'd'; 'c'; 'a']; ['a'; 'd'; 'b'; 'c']; ['d'; 'a'; 'b'; 'c'];
// ['d'; 'b'; 'a'; 'c']; ['d'; 'b'; 'c'; 'a']; ['a'; 'd'; 'c'; 'b'];
// ['d'; 'a'; 'c'; 'b']; ['d'; 'c'; 'a'; 'b']; ['d'; 'c'; 'b'; 'a']]
permutations [3;3] |> Seq.toList
// [[3; 3]; [3; 3]]
\end{minted}
Excellent! Now let's run FsCheck:
\begin{minted}{fsharp}
Check.Quick (``a sorted list is always a permutation of the original
list`` goodSort)
\end{minted}
Hmmm. That's funny, nothing seems to be happening. And my CPU is maxing
out for some reason. What's going on?
What's going on is that you are going to be sitting there for a long
time! If you are following along at home, I suggest you right-click and
cancel the interactive session now.
The innocent looking \texttt{permutations} is really \emph{really} slow
for any normal sized list. For example, a list of just 10 items has
3,628,800 permutations. While with 20 items, you are getting to
astronomical numbers.
And of course, FsCheck will be doing hundreds of these tests! So this
leads to an important tip:
\textbf{TIP: Make sure your property checks are very fast. You will be
running them a LOT!}
We've already seen that even in the best case, FsCheck will evaluate the
property 100 times. And if shrinking is needed, even more. So make sure
your tests are fast to run!
But what happens if you are dealing with real systems such as databases,
networks, or other slow dependencies?
In his (highly recommended) \href{http://vimeo.com/68383317}{video on
using QuickCheck}, John Hughes tells of when his team was trying to
detect flaws in a distributed data store that could be caused by network
partitions and node failures.
Of course, killing real nodes thousands of times was too slow, so they
extracted the core logic into a virtual model, and tested that instead.
As a result, the code was \emph{later refactored} to make this kind of
testing easier. In other words, property-based testing influenced the
design of the code, just as TDD would.
\subsection{Sort invariant - 3rd
attempt}
\label{sort-invariant---3rd-attempt}
Ok, so we can't use permutations by just looping through them. So let's
use the same idea but write a function that is specific for this case, a
\texttt{isPermutationOf} function.
\begin{minted}{fsharp}
let ``a sorted list has same contents as the original list`` sortFn
(aList:int list) =
let sorted = aList |> sortFn
isPermutationOf aList sorted
\end{minted}
Here's the code for \texttt{isPermutationOf} and its associated helper
functions:
\begin{minted}{fsharp}
/// Given an element and a list, and other elements previously
/// skipped,
/// return a new list without the specified element.
/// If not found, return None
let rec withoutElementRec anElement aList skipped =
match aList with
| [] -> None
| head::tail when anElement = head ->
// matched, so create a new list from the skipped and
// the remaining
// and return it
let skipped' = List.rev skipped
Some (skipped' @ tail)
| head::tail ->
// no match, so prepend head to the skipped and recurse
let skipped' = head :: skipped
withoutElementRec anElement tail skipped'
/// Given an element and a list
/// return a new list without the specified element.
/// If not found, return None
let withoutElement x aList =
withoutElementRec x aList []
/// Given two lists, return true if they have the same contents
/// regardless of order
let rec isPermutationOf list1 list2 =
match list1 with
| [] -> List.isEmpty list2 // if both empty, true
| h1::t1 ->
match withoutElement h1 list2 with
| None -> false
| Some t2 ->
isPermutationOf t1 t2
\end{minted}
Let's try the test again. And yes, this time it completes before the
heat death of the universe.
\begin{minted}{fsharp}
Check.Quick (``a sorted list has same contents as the original list``
goodSort)
// Ok, passed 100 tests.
\end{minted}
What's also great is that the malicious implementation now fails to
satisfy this property!
\begin{minted}{fsharp}
Check.Quick (``a sorted list has same contents as the original list``
badSort2)
// Falsifiable, after 2 tests (5 shrinks)
// [1; 0]
\end{minted}
In fact, these two properties,
\texttt{adjacent\ pairs\ from\ a\ list\ should\ be\ ordered} and
\texttt{a\ sorted\ list\ has\ same\ contents\ as\ the\ original\ list}
should indeed ensure that any implementation is correct.
\section{Sidebar: Combining
properties}
\label{sidebar-combining-properties}
Just above, we noted that there were \emph{two} properties needed to
define the ``is sorted'' property. It would be nice if we could combine
them into one property \texttt{is\ sorted} so that we can have a single
test.
Well, of course we can always merge the two sets of code into one
function, but it's preferable to keep functions as small as possible.
Furthermore, a property like \texttt{has\ same\ contents} might be
reusable in other contexts as well.
What we want then, is an equivalent to \texttt{AND} and \texttt{OR} that
is designed to work with properties.
FsCheck to the rescue! There are built in operators to combine
properties: \texttt{.\&.} for \texttt{AND} and \texttt{.\textbar{}.} for
\texttt{OR}.
Here is an example of them in use:
\begin{minted}{fsharp}
let ``list is sorted``sortFn (aList:int list) =
let prop1 = ``adjacent pairs from a list should be ordered`` sortFn
aList
let prop2 = ``a sorted list has same contents as the original list``
sortFn aList
prop1 .&. prop2
\end{minted}
When we test the combined property with a good implementation of
\texttt{sort}, everything works as expected.
\begin{minted}{fsharp}
let goodSort = List.sort
Check.Quick (``list is sorted`` goodSort )
// Ok, passed 100 tests.
\end{minted}
And if we test a bad implementation, the combined property fails as
well.
\begin{minted}{fsharp}
let badSort aList = []
Check.Quick (``list is sorted`` badSort )
// Falsifiable, after 1 test (0 shrinks)
// [0]
\end{minted}
But there's a problem now. Which of the two properties failed?
What we would like to do is add a ``label'' to each property so that we
can tell them apart. In FsCheck, this is done with the
\texttt{\textbar{}@} operator:
\begin{minted}{fsharp}
let ``list is sorted (labelled)``sortFn (aList:int list) =
let prop1 = ``adjacent pairs from a list should be ordered`` sortFn
aList
|@ "adjacent pairs from a list should be ordered"
let prop2 = ``a sorted list has same contents as the original list``
sortFn aList
|@ "a sorted list has same contents as the original list"
prop1 .&. prop2
\end{minted}
And now, when we test with the bad sort, we get a message
\texttt{Label\ of\ failing\ property:\ a\ sorted\ list\ has\ same\ contents\ as\ the\
original\ list}:
\begin{minted}{fsharp}
Check.Quick (``list is sorted (labelled)`` badSort )
// Falsifiable, after 1 test (2 shrinks)
// Label of failing property: a sorted list has same contents as the
// original list
// [0]
\end{minted}
For more on these operators,
\href{https://fscheck.github.io/FsCheck/Properties.html\#And-Or-and-Labels}{see
the FsCheck documentation under ``And, Or and Labels''}.
And now, back to the property-divising strategies.
\section{``Solving a smaller
problem''}
\label{solving-a-smaller-problem2}
Sometimes you have a recursive data structure or a recursive problem. In
these cases, you can often find a property that is true of a smaller
part.
For example, for a sort, we could say something like:
\begin{verbatim}
A list is sorted if:
* The first element is smaller (or equal to) the second.
* The rest of the elements after the first element are also sorted.
\end{verbatim}
Here is that logic expressed in code:
\begin{minted}{fsharp}
let rec ``First element is <= than second, and tail is also sorted``
sortFn (aList:int list) =
let sortedList = aList |> sortFn
match sortedList with
| [] -> true
| [first] -> true
| [first;second] ->
first <= second
| first::second::tail ->
first <= second &&
let subList = second::tail
``First element is <= than second, and tail is also sorted``
sortFn subList
\end{minted}
This property is satisfied by the real sort function:
\begin{minted}{fsharp}
let goodSort = List.sort
Check.Quick (``First element is <= than second, and tail is also sorted``
goodSort )
// Ok, passed 100 tests.
\end{minted}
But unfortunately, just like previous examples, the malicious
implementations also pass.
\begin{minted}{fsharp}
let badSort aList = []
Check.Quick (``First element is <= than second, and tail is also sorted``
badSort )
// Ok, passed 100 tests.
let badSort2 aList =
match aList with
| [] -> []
| head::_ -> List.replicate (List.length aList) head
Check.Quick (``First element is <= than second, and tail is also sorted``
badSort2)
// Ok, passed 100 tests.
\end{minted}