Excerpt from:  Chris Marinos
.
April 20, 2009

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 a 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:

BDDTestResults

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.

Topic Tags:  , ,

Syndication OptionsRSS (Rich Site Summary) Feed Atom Feed OPML (Outline Processor Language) Feed MYST-ML (MyST Markup Language) Content Feed MS-Office Smart Tag Subscription