Writing BDD-Style tests in F# with xUnit.net

Recently, I’ve been exploring different options for testing in F#. I was pleased to find out that there has been some good work done in this space already:

  • Matthew Podowysoki’s Functional Programming Unit Testing series is a great overview of testing in F#.
  • The FsUnit project is a specification style testing framework written in F#.
  • FsCheck is a port of Haskell’s QuickCheck to F#. QuickCheck is a tool for randomly generating tests.

One of the things that I didn’t find was a good solution for BDD-style testing. I’m not a BDD fanatic, but I am definitely a fan of the extra mile that BDD frameworks go to blend tests and specifications. FsUnit has support for specification style tests, but as a whole, I felt that the project was too young to be an ideal solution. Plus, I didn’t have the desire to start learning and using yet another testing framework.

Using xUnit.net as a starting point, I decided to try writing my own BDD extension.

Let’s say that I have a two dimensional vector type that I want to test:

    type Vector = 
        { X : float;
          Y : float; }
    module VectorOperations =
        let Add one two =
            { X = one.X + two.X; Y = one.Y + two.Y }

The syntax that I came up with looks like this:

let context = Given "a zero vector, v1," {X=0.0; Y=0.0}

[<BDDFact>]
let VectorAdditon () =
    let outcome = context.With "a vector, v2, of (1,1) added to it" (fun vector -> vector + {X=1.0; Y=1.0})
    outcome.Observe "the result is a new vector = (v1.x + v2.x, v1.y+v2.y)." (fun _ result -> Assert.Equal({X=1.0; Y=1.0}, result))

This syntax is very similar to the syntax that NBehave uses, but I like that it does not rely on a combination of closure and mutable state to pass information from between the “given”, “with”, and “then/assert/observe” portions of the test. I also like that I don’t have to rely on class hierarchies and a bulky testing framework to get the BDD style naming that I want.

Here’s the full implementation:

#light

namespace BDDExtensions

open System
open System.Reflection
open System.Xml
open Xunit
open Xunit.Sdk

module BDDExtensions =
    let mutable Observations = []
    
    type Outcome<'a, 'b>(message, context, result) =
        member this.Observe observationMessage (action:'a -> 'b-> unit) =
            Observations <- (message, (fun () -> action context result)) :: Observations
            
    type Context<'a>(message:string, value) =
        member this.With withMessage func =
            new Outcome<'a, 'b>(String.Concat([|message; "with"; withMessage|]), value, func(value))
         
    let Given (message:string) (value:'a) = new Context<'a>(String.Concat("Given ", message), value)
    
    type BDDCommand(assertion, displayName, info) =
        interface ITestCommand with
            member this.ShouldCreateInstance = false
            member this.Execute testClass = 
                try
                    assertion()
                    new PassedResult(info, displayName) :> MethodResult
                with 
                    ex -> new FailedResult(info, ex, displayName) :> MethodResult
            member this.DisplayName = displayName
            member this.ToStartXml () =
                     let doc = new XmlDocument()
                     doc.LoadXml("<dummy/>")
                     let testNode = XmlUtility.AddElement(doc.ChildNodes.[0], "start")
                     XmlUtility.AddAttribute(testNode, "name", displayName)
                     XmlUtility.AddAttribute(testNode, "type", info.ReflectedType.FullName)
                     XmlUtility.AddAttribute(testNode, "method", info.Name)
                     testNode 
                     
    [<AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)>]
    type BDDFactAttribute() =
        inherit FactAttribute()
        override this.EnumerateTestCommands(info:MethodInfo) =
            info.Invoke(null, null) |> ignore
            Observations
            |> Seq.map (fun (name, assertion) -> new BDDCommand(assertion, name, info))
            |> Seq.cast<ITestCommand>

As displayed in the Vector example above, the first thing that we do is call the Given function. This sets up a Context that takes a function to arrange the test. Next, we call the With method on our Context to perform the operation that generates the result we want to test. Then we check as many conditions as we need by using the Observe function on the Outcome we created. Each outcome adds an action to the mutable Observations list.

When the xUnit framework encounters a BDDFactAttribute on a method, it first executes the method to populate the list of observations. For each observation, we return a BDDCommand. The xUnit framework then executes each command as a separate test case. This causes the command’s action to be executed and any failed assertions to throw errors which the xUnit framework displays in its test output.

Here’s the result. As you can see, the test name is extremely readable:

F# BDD Results

The code is definitely a little rough around the edges (*cough* mutable static variable *cough*). As always, I was impressed by how few lines of F# code it took to do what I wanted, but I was also impressed by how easy it was to extend xUnit.

This entry was posted in F#, testing, xunit. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*
*