[Announce] FsStory, executable stories in F#

Since Claudio Perrone’s talk on Øredev, I have been thinking about what his MisBehave would look like in F#. In his talk, Claudio also mentioned Cucumber, a story runner written in Ruby. My plan was to make a lightweight DSL for writing stories in F# code, with the story parts separated from the implementation parts.

Currently, FsStory enables the developer to write user story scenarios (in Given/When/Then form) in F# code, like this:

#light

open FsStoryRunner
open MutatedTurtleMovesImpl
open Xunit

(*
In order to impress my friends
As a .NET programmer
I want to draw funny fractal pictures
*)

[<fact>]
let MoveTurtleToPosition() =
given ATurtle
|> andGiven IsRotated90DegreesToTheRight
|> whens (TurtleWalksSteps 9)
|> thens (TurtleIsLocatedAt (0,9))
|> endStory

Did you notice [<fact>] attribute just before the function definition? It is a xUnit.net specific attribute, telling xUnit.net that the function is a runnable test. So, why xUnit.net? Answer: xUnit.net is the currently the only test framework that runs static test methods, which is what F# functions compiles to.

Note: If you think that the story above is too low level to be a “good” user story, you’re right, but it’s just an example..

The “ATurtle”, “IsRotated90DegreesToTheRight”, “TurtleWalksSteps”, etc, are functions that you have to implement yourself. What these functions do is not FsStory’s business, except that they have the same type. It’s a good thing to think about this in advance.

If you’re testing something object oriented, i.e. a C# project, then you’re probably have to let the functions have the type () -> (). That is, they take no argument and return void, in C# lingo. You’d also need a mutable variable to accomplish this.

#light

open Turtle
open FsxUnit.Syntax

let mutable turtle = new Turtle() // turtle must have type Turtle

let ATurtle () = turtle <- new Turtle() // For reuse in same story let MovesOneStepForward () = turtle.Go() let IsMovedOneStepForward () = turtle.Position.X |> should equal 1 let RotationIs angle () = turtle.Direction |> should equal 0.0

It’s up to the developer what library she wants to use for her assertions. In this example, FsTest was used, but she could go for NUnit or NBehave or something else. I hadn’t actually tried this and do not longer think this will work. Either use xUnit.net or FsTest (which is based on xUnit.net).

Another style is to work with immutable objects. One example of immutable objects are value objects, in DDD. Immutable objects correspond well to functional programming principles. Here is an example of an implementation of a scenario when an immutable object is used in the SUT (System Under Test).

let ATurtle () = new TurtleImmutable()
let IsRotated90DegreesToTheRight = fun (turtle : TurtleImmutable) -> turtle.Left()
let TurtleWalksSteps steps = fun (turtle : TurtleImmutable) -> turtle.GoSteps(steps)
let TurtleIsLocatedAt (x,y) = fun (turtle : TurtleImmutable) -> turtle.Position |>
should equal (new Position(x,y)) ; turtle

To clarify, all methods on the (immutable) turtle return a new turtle and that turtle is returned and then passed in as an argument to the next test function (by FsStory). As you might have spotted, the example uses a lambda, an anonymous functions (the “fun”) instead of specifying an argument explicitly. It’s a good thing to get a running story before actually implementing the logic and assertions. Using the function “id” (just returning the argument) on the right-hand side is very helpful for getting everything to run.

You can find FsStory at http://www.codeplex.com/fsstory.

Advertisements

About gustafnk

Developer at Jayway, REST/Hypermedia, AWD, Software apprentice, Dvorak user, Christian atheist, Zizek fan. I'm on twitter: @gustaf_nk
This entry was posted in bdd, F#, FsStory. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s