F# Testing DSL Part 3

I ended the last post by proposing a syntax for asserting against floating point results:

let ``Comparing floating point numbers``() =
    Given 1.0 |>
    When ``dividing by 3`` |>
    Then it should be (0.333333333 +/- 0.00000001)

How can we implement this? Let’s start with the trivial part: the be function. It is just another “filler” word like it and should:

 let be f = f

That leaves (0.333333333 +/- 0.00000001) to perform the same role as the ``return`` function implemented previously i.e. it is a custom overloaded operator that should have a signature 'a -> TestContext<'a> -> unit. If we restrict 'a to be a float, we could implement it as:

let (+/-) (expected: float) (tolerance: float) (context: TestContext) =
     Assert.AreEqual(expected, context.ToState, tolerance)

Then, assuming we have also defined let ``dividing by 3`` = n / 3.0, the above test passes. However, we will want to comparing other types of floating point numbers, not just floats. Unfortunately we can’t just do this:

let (+/-) expected tolerance (context: TestContext<_>) =
     Assert.AreEqual(expected, context.ToState, tolerance)

The compiler can’t determine which overload of AreEqual to use. Instead, we need to somehow write generic code that is constrained to work with any numeric type. This answer on StackOverflow shows how to use inline functions, explicit member constraints and statically resolved type parameters to (messily) achieve the required result:

let inline (+/-) (expected) (tolerance) (context: TestContext<_>) =
    let zero = LanguagePrimitives.GenericZero
    let actual = context.ToState
    let absDiff = if expected > actual then
      		      expected - actual
		  else
		      actual - expected

    let absTolerance = if tolerance < zero then
		           -tolerance
		       else
			   tolerance

    if absDiff < absTolerance then
        ()
    else
        let msg = sprintf "Expected: %s +/- %s\nBut was:  %s" <| expected.ToString() <| tolerance.ToString() <| actual.ToString()
	raise <| AssertionException(msg)

We basically have to implement Assert.AreEqual(expected, actual, tolerance) ourselves, and I’ve chosen to throw the same exception that NUnit would throw so that it can be a drop-in replacement.

If we then implement ``dividing by 3`` in a similar generic manner:

let inline ``dividing by 3`` (n:^a) : ^a =
    let three:^a = (Seq.init 3 (fun _ -> LanguagePrimitives.GenericOne)) |> Seq.sum
    n / three

Then both of the tests below pass:

[<Test>]
let ``Comparing floats``() =
    Given 1.0 |>
    When ``dividing by 3`` |>
    Then it should be (0.333333333 +/- 0.00000001)

[<Test>]
let ``Comparing decimals``() =
    Given 1.0m |>
    When ``dividing by 3`` |>
    Then it should be (0.333333333m +/- 0.00000001m)

This brings me to the end of this series of posts. Hopefully I’ve illustrated one of the ways in which F# makes it (relatively) easy to write an internal DSL, as well as whetting your appetite for exploring the possibilities of low-ceremony BDD in F#.

One thought on “F# Testing DSL Part 3

  1. Pingback: F# Weekly #14, 2013 | Sergey Tihon's Blog

Leave a Reply