Property-Based Testing With FsCheck

Introduction

This article introduces the concept of property-based testing which is another approach to verify that a function provides an expected result. The complete source code of the project can be accessed on GitHub.

False safety of traditional unit-testing approach

Let's imagine that we have an array of musicians where we define a musician by his/her name and an overall number of people who have listened to that musician:
  1. type Artist = {  
  2.         name: string  
  3.         listeners: int  
  4.     }  
And also, we have a function that orders a descendant array of type above by listeners count.
  1. let orderArtistsByListenersCount artists =  
  2.         let ordered =   
  3.                 artists  
  4.                     |> Array.sortBy (fun i -> -i.listeners)  
  5.         Success(ordered)  
We could write a unit-test to verify that this function works correctly with xUnit just as below.
  1. [<Fact>]  
  2.     let orderArtistsByListenersCount_returns_expected_result() =  
  3.         let Satie = {name = "Erik Satie"; listeners = 750000}  
  4.         let Chopin = {name ="Frederic Chopin"; listeners = 1200000}  
  5.         let Barber = {name = "Samuel Barber"; listeners = 371000}  
  6.         let artists = [|Satie; Chopin; Barber|]  
  7.         let result = orderArtistsByListenersCount artists  
  8.         match result with  
  9.         | Success s ->   
  10.             Assert.Equal(s.[0], Chopin)  
  11.             Assert.Equal(s.[1], Satie)  
  12.             Assert.Equal(s.[2], Barber)  
  13.         | Failure _ -> Assert.True(false)  
Looks like we're fine now but in fact, nothing stops another developer from implementing a function which just satisfies the magic numbers provided in test.
  1. let orderArtistsByListenersCount artists =  
  2.         let Satie = {name = "Erik Satie"; listeners = 750000}  
  3.         let Chopin = {name ="Frederic Chopin"; listeners = 1200000}  
  4.         let Barber = {name = "Samuel Barber"; listeners = 371000}  
  5.         let artists = [|Chopin; Satie; Barber|]  
  6.         Success(artists)  
Actually, nothing verifies sortedness of an array.

Property-based testing with FsCheck

On the contrary, property-based testing verifies whether our result satisfies the properties that we define. For example, the below code checks whether each item of the sorted array has bigger or same amount of listeners as the next item
  1. open FsCheck.Xunit  
  2.   
  3. let ``pairs from collection should be ordered`` orderFn artists =  
  4.         let orderedArtists = artists |> orderFn  
  5.         match orderedArtists with  
  6.         | Success s -> s |> Array.pairwise |> Array.forall (fun (x,y) -> x.listeners >= y.listeners)  
  7.         | Failure _ -> false  
  8.       
  9. [<Property>]  
  10. let pairwise x =  
  11.         ``pairs from collection should be ordered`` orderArtistsByListenersCount x  
What FsCheck does is that it generates several hundred random inputs to test that the property is satisfied upon all possible input values.

Next example checks whether the result array contains same items as the input array.
  1. let ``should be permutation of original elements`` orderFn artists =  
  2.         let orderedArtists = artists |> orderFn  
  3.         match orderedArtists with  
  4.         | Success s -> s |> List.ofArray |> isPermutationOf (List.ofArray artists)  
  5.         | Failure _ -> false  
  6.           
  7. [<Property>]  
  8. let isPermutation x =  
  9.         ``should be permutation of original elements`` orderArtistsByListenersCount x  
 Implementation of isPermutation can be found here.

Conclusion

Examples of the code above verify not just that the function complies with some magic-number inputs but examine fundamental properties of the result, which adds extra security to your tests.