An example of how to constrain generated input with FsCheck.

This article is the fourth in a series of articles that demonstrate how to develop software using types and properties. In the previous article, you saw how to express properties for a simple state transition: finding the next tennis score when the current score is advantage to a player. These properties were simple, because they had to hold for all input of the given type (Player). In this article, you'll see how to constrain the input that FsCheck generates, in order to express properties about tennis scores where one of the players have forty points.

The source code for this article series is available on GitHub.

Winning the game

When one of the players have forty points, there are three possible outcomes of the next ball:

  • If the player with forty points wins the ball, (s)he wins the game.
  • If the other player has thirty points, and wins the ball, the score is deuce.
  • If the other player has less than thirty points, and wins the ball, his or her points increases to the next level (from love to fifteen, or from fifteen to thirty).
The first property is the easiest to express, because it doesn't depend on the other player's points.

You may recall that when one of the players have forty points, you express that score with the FortyData record type:

type FortyData = { Player : Player; OtherPlayerPoint : Point }

Since Point is defined as Love | Fifteen | Thirty, it's clear that the Player who has forty points has a higher score than the other player - regardless of the OtherPlayerPoint value. This means that if that player wins, (s)he wins the game. It's easy to express that property with FsCheck:

[<Property>]
let ``Given player: 40 when player wins then score is correct``
    (current : FortyData) =
 
    let actual = scoreWhenForty current current.Player
 
    let expected = Game current.Player
    expected =! actual

Notice that this test function takes a single function argument called current, of the type FortyData. Since illegal states are unrepresentable, FsCheck can only generate legal values of that type.

The scoreWhenForty function is a function that explicitly models what happens when the score is in the Forty case. The first argument is the data associated with the current score: current. The second argument is the winner of the next ball. In this test case, you want to express the case where the winner is the player who already has forty points: current.Player.

The expected outcome of this is always that current.Player wins the game.

The =! operator is a custom operator defined by Unquote, an assertion library. You can read the expression as expected must equal actual.

In order to pass this property, you must implement a scoreWhenForty function. This is the simplest implementation that could possible work:

let scoreWhenForty current winner = Game winner

While this passes all tests, it's obviously not a complete implementation.

Getting even

Another outcome of a Forty score is that if the other player has thirty points, and wins the ball, the new score is deuce. Expressing this as a property is only slightly more involved:

[<Property>]
let ``Given player: 40 - other: 30 when other wins then score is correct``
    (current : FortyData) =
 
    let current = { current with OtherPlayerPoint = Thirty }
    let actual = scoreWhenForty current (other current.Player)
    Deuce =! actual

In this test case, the other player specifically has thirty points. There's no variability involved, so you can simply set OtherPlayerPoint to Thirty.

Notice that instead of creating a new FortyData value from scratch, this property takes current (which is generated by FsCheck) and uses a copy-and-update expression to explicitly bind OtherPlayerPoint to Thirty. This ensures that all other values of current can vary, but OtherPlayerPoint is fixed.

The first line of code shadows the current argument by binding the result of the copy-and-update expression to a new value, also called current. Shadowing means that the original current argument is no longer available in the rest of the scope. This is exactly what you want, because the function argument isn't guaranteed to model the test case where the other player has forty points. Instead, it can be any FortyData value. You can think of the argument provided by FsCheck as a seed used to arrange the Test Fixture.

The property proceeds to invoke the scoreWhenForty function with the current score, and indicating that the other player wins the ball. You saw the other function in the previous article, but it's so small that it can be repeated here without taking up much space:

let other = function PlayerOne -> PlayerTwo | PlayerTwo -> PlayerOne

Finally, the property asserts that deuce must equal actual. In other words, the expected result is deuce.

This property fails until you fix the implementation:

let scoreWhenForty current winner =
    if current.Player = winner
    then Game winner
    else Deuce

This is a step in the right direction, but still not the complete implementation. If the other player has only love or fifteen points, and wins the ball, the new score shouldn't be deuce.

Incrementing points

The last test case is where it gets interesting. The situation you need to test is that one of the players has forty points, and the other player has either love or fifteen points. This feels like the previous case, but has more variable parts. In the previous test case (above), the other player always had thirty points, but in this test case, the other player's points can vary within a constrained range.

Perhaps you've noticed that so far, you haven't seen any examples of using FsCheck's API. Until now, we've been able to express properties from values generated by FsCheck without constraints. This is no longer possible, but fortunately, FsCheck comes with an excellent API that enables you to configure it. Here, you'll see how to configure it to create values from a proper subset of all possible values:

[<Property>]
let ``Given player: 40 - other: < 30 when other wins then score is correct``
    (current : FortyData) =
 
    let opp = Gen.elements [LoveFifteen] |> Arb.fromGen
    Prop.forAll opp (fun otherPlayerPoint ->
        let current = { current with OtherPlayerPoint = otherPlayerPoint }
 
        let actual = scoreWhenForty current (other current.Player)
 
        let expected =
            incrementPoint current.OtherPlayerPoint
            |> Option.map (fun np -> { current with OtherPlayerPoint = np })
            |> Option.map Forty
        expected =! Some actual)

That's only nine lines of code, and some of it is similar to the previous property you saw (above). Still, F# code can have a high degree of information density, so I'll walk you through it.

FsCheck's API is centred around Generators and Arbitraries. Ultimately, when you need to configure FsCheck, you'll need to define an Arbitrary<'a>, but you'll often use a Gen<'a> value to do that. In this test case, you need to tell FsCheck to use only the values Love and Fifteen when generating Point values.

This is done in the first line of code. Gen.elements creates a Generator that creates random values by drawing from a sequence of possible values. Here, we pass it a list of two values: Love and Fifteen. Because both of these values are of the type Point, the result is a value of the type Gen<Point>. This Generator is piped to Arb.fromGen, which turns it into an Arbitrary (for now, you don't have to worry about exactly what that means). Thus, opp is a value of type Arbitrary<Point>.

You can now take that Arbitrary and state that, for all values created by it, a particular property must hold. This is what Prop.forAll does. The first argument passed is opp, and the second argument is a function that expresses the property. When the test runs, FsCheck call this function 100 times (by default), each time passing a random value generated by opp.

The next couple of lines are similar to code you've seen before. As in the case where the other player had thirty points, you can shadow the current argument with a new value where the other player's points is set to a value drawn from opp; that is, Love or Fifteen.

Notice how the original current value comes from an argument to the containing test function, whereas otherPlayerPoint comes from the opp Arbitrary. FsCheck is smart enough to enable this combination, so you still get the variation implied by combining these two sources of random data.

The actual value is bound to the result of calling scoreWhenForty with the current score, and indicating that the other player wins the ball.

The expected outcome is a new Forty value that originates from current, but with the other player's points incremented. There is, however, something odd-looking going on with Option.map - and where did that incrementPoint function come from?

In the previous article, you saw how sometimes, a test triggers the creation of a new helper function. Sometimes, such a helper function is of such general utility that it makes sense to put it in the 'production code'. Previously, it was the other function. Now, it's the incrementPoint function.

Before I show you the implementation of the incrementPoint function, I would like to suggest that you reflect on it. The purpose of this function is to return the point that comes after a given point. Do you remember how, in the article on designing with types, we quickly realised that love, fifteen, thirty, and forty are mere labels; that we don't need to do arithmetic on these values?

There's one piece of 'arithmetic' you need to do with these values, after all: you must be able to 'add one' to a value, in order to get the next value. That's the purpose of the incrementPoint function: given Love, it'll return Fifteen, and so on - but with a twist!

What should it return when given Thirty as input? Forty? That's not possible. There's no Point value higher than Thirty. Forty doesn't exist.

The object-oriented answer would be to throw an exception, but in functional programming, we don't like such arbitrary jump statements in our code. GOTO is, after all, considered harmful.

Instead, we can return None, but that means that we must wrap all the other return values in Some:

let incrementPoint = function
    | Love -> Some Fifteen
    | Fifteen -> Some Thirty
    | Thirty -> None

This function has the type Point -> Point option, and it behaves like this:

> incrementPoint Love;;
val it : Point option = Some Fifteen
> incrementPoint Fifteen;;
val it : Point option = Some Thirty
> incrementPoint Thirty;;
val it : Point option = None

Back to the property: the expected value is that the other player's points are incremented, and this new points value (np, for New Points) is bound to OtherPlayerPoint in a copy-and-update expression, using current as the original value. In other words, this expression returns the current score, with the only change that OtherPlayerPoint now has the incremented Point value.

This has to happen inside of an Option.map, though, because incrementPoint may return None. Furthermore, the new value created from current is of the type FortyData, but you need a Forty value. This can be achieved by piping the option value into another map that composes the Forty case constructor.

The expected value has the type Score option, so in order to be able to compare it to actual, which is 'only' a Score value, you need to make actual a Score option value as well. This is the reason expected is compared to Some actual.

One implementation that passes this and all previous properties is:

let scoreWhenForty current winner =
    if current.Player = winner
    then Game winner
    else
        match incrementPoint current.OtherPlayerPoint with
        | Some p -> Forty { current with OtherPlayerPoint = p }
        | None -> Deuce

Notice that the implementation also uses the incrementPoint function.

To be continued...

In this article, you saw how, even when illegal states are unrepresentable, you may need to further constrain the input into a property in order to express a particular test case. The FsCheck combinator library can be used to do that. It's flexible and well thought-out.

While I could go on and show you how to express properties for more state transitions, you've now seen the most important techniques. If you want to see more of the tennis state transitions, you can always check out the source code accompanying this article series.

In the next article, instead, you'll see how to compose all these functions into a system that implements the tennis scoring rules.

If you're interested in learning more about Property-Based Testing, you can watch my introduction to Property-based Testing with F# Pluralsight course.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or Google Plus, or somewhere else with a permalink. Ping me with the link, and I may add it as a comment.

Published

Monday, 15 February 2016 09:08:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!