Unit testing in F#
I'm one really into testing. I love it. I love refactoring and I love the structure I get from testing. But always there's this wall between the tests and the reality. Most often it is the programming language that stands in your way, and you have to write test names like this.
public class GameOfLife
{
public void CellsWithNoNeighboursShouldDie() { ... }
}
The main problem here is readability. Language constructs (public class/void) and limitations (pascal case) stands in your way of readability. This is both in writing the test and reading the test output.
What test output really should look like is this
You can accieve this by writing your tests in F#. I have done so here with the frameworks FSUnit and xUnit. This is what the test code looks like.
namespace GameOfLife
module TestHelpers =
open NHamcrest
let intersect list = CustomMatcher<obj>(sprintf "Intersect %A" list, fun a -> list |> List.forall (fun item -> (unbox a) |> List.exists ((=) item)))
// 1. Any live cell with fewer than two live neighbours dies, as if caused by under-population.
namespace ``1 Any live cell with fewer than two live neighbours dies as if caused by under-population``
open Xunit;
open FsUnit.Xunit;
open GameOfLife
type ``Given a cell with no neighbours`` () =
[<Fact>]
let ``when turn is run, the cell dies`` () =
Run.next [0, 0] |> should equal List.empty<int * int>
type ``Given two cells that are each other neighbours`` () =
[<Fact>]
let ``when turn is run, the cells dies`` () =
Run.next [0, 0; 0, 1] |> should equal List.empty<int * int>
type ``Given two cells that are not each other neighbours`` () =
[<Fact>]
let ``when turn is run, the cells dies`` () =
Run.next [0, 0; 0, 2] |> should equal List.empty<int * int>
type ``Given three cells where two are neighbours and one alone`` () =
[<Fact>]
let ``when turn is run, none lives on`` () =
Run.next [0, 0; 0, 1; 3, 3] |> should equal List.empty<int * int>
// 2. Any live cell with two or three live neighbours lives on to the next generation.
namespace ``2 Any live cell with two or three live neighbours lives on to the next generation``
open Xunit;
open FsUnit.Xunit;
open GameOfLife
open TestHelpers
type ``Given three cells that are each others neighbours`` () =
let row = [0, 0; 0, 1; 1, 0]
[<Fact>]
let ``when turn is run, the cells lives on`` () =
Run.next row |> should intersect row
type `` Given four cells in a cluster`` () =
[<Fact>]
let ``when turn is run, all lives on`` () =
let cluster = [0, 0; 0, 1; 1, 0; 1, 1]
Run.next cluster |> should equal cluster
type ``Given four cells on a row`` () =
let row = [0, 0; 1, 0; 2, 0; 3, 0]
[<Fact>]
let ``when turn is run, two in the middle lives on`` () =
Run.next row |> should intersect [1, 0; 2, 0]
// 3. Any live cell with more than three live neighbours dies, as if by overcrowding.
namespace ``3 Any live cell with more than three live neighbours dies as if by overcrowding``
open Xunit;
open FsUnit.Xunit;
open GameOfLife
open TestHelpers
type ``Given a 3x3 block of cells`` () =
let row = [(-1, -1); (-1, 0); (-1, 1); (0, -1); (0, 0); (0, 1); (1, -1); (1, 0); (1, 1)]
[<Fact>]
let ``when first turn is run, the corners will remain`` () =
Run.next row |> should intersect [-1, -1; -1, 1; 1, -1; 1, 1]
namespace ``4 Any dead cell with exactly three live neighbours becomes a live cell as if by reproduction``
open Xunit;
open FsUnit.Xunit;
open GameOfLife
open TestHelpers
type ``Given a dead cell with three (not neighbours) as neighbours`` () =
[<Fact>]
let ``when turn is run, the dead cell becomes live and lonely cells dies`` () =
Run.next [0, 0; -1, -2; 1, -2] |> should equal [0, -1]
type ``Given a row of three cells`` () =
[<Fact>]
let ``when turn is run, the two dead cells over and under the middle cell becomes live cells`` () =
Run.next [-1, 0; 0, 0; 1, 0] |> should intersect [0, 1; 0, -1]
namespace ``Examples: Still lifes``
open Xunit;
open FsUnit.Xunit;
open GameOfLife
type ``Given a block of four cells`` () =
let block = [0, 0; 1, 0; 1, 1; 0, 1]
[<Fact>]
let ``when turn is run, the state doesn't change`` () =
Run.next block |> should equal block
type ``Given a beehive of six cells`` () =
let beehive = [0, 0; 1, 1; 2, 1; 3, 0; 2, -1; 1, -1]
[<Fact>]
let ``when turn is run, the state doesn't change`` () =
Run.next beehive |> should equal beehive
type ``Given a loaf of seven cells`` () =
let loaf = [1, 0; 2, 0; 3, -1; 3, -2; 2, -3; 1, -2; 0, -1]
[<Fact>]
let ``when turn is run, the state doesn't change`` () =
Run.next loaf |> should equal loaf
type ``Given a boat of five cells`` () =
let boat = [0, 0; 1, 0; 2, -1; 1, -2; 0, -1]
[<Fact>]
let ``when turn is run, the state doesn't change`` () =
Run.next boat |> should equal boat
namespace ``Examples: Oscillators``
open Xunit;
open FsUnit.Xunit;
open GameOfLife
type ``Given a blinker of three cells`` () =
let blinker = [-1, 0; 0, 0; 1, 0]
[<Fact>]
let ``when first turn is run, the blinker becomes horizontal`` () =
Run.next blinker |> List.sort |> should equal [0, -1; 0, 0; 0, 1]
[<Fact>]
let ``when second turn is run, the blinker goes back to its original state`` () =
blinker |> Run.next |> Run.next |> List.sort |> should equal blinker
type ``Given a toad of six cells`` () =
let toad = [0, -1; 1, -1; 1, 0; 2, -1; 2, 0; 3, 0]
[<Fact>]
let ``when first turn is run, the toad explodes`` () =
Run.next toad |> List.sort |> should equal [0, -1; 0, 0; 1, -2; 2, 1; 3, -1; 3, 0]
[<Fact>]
let ``when second turn is run, the toad returns to its initial state`` () =
toad |> Run.next |> Run.next |> List.sort |> should equal toad
type ``Given a beacon of six cells`` () =
let beacon = [0, -1; 0, 0; 1, 0; 2, -3; 3, -3; 3, -2]
[<Fact>]
let ``when first turn is run, will complete the beacon with two additional cells`` () =
Run.next beacon |> List.sort |> should equal [0, -1; 0, 0; 1, -1; 1, 0; 2, -3; 2, -2; 3, -3; 3, -2]
[<Fact>]
let ``when second turn is run, the beacon returns to its initial state`` () =
beacon |> Run.next |> Run.next |> List.sort |> should equal beacon
type ``Given a pulsar of 48 cells`` () =
let pulsar =
[
[2, 0; 3, 0; 4, 0; 8, 0; 9, 0; 10, 0];
[0, -2; 5, -2; 7, -2; 12, -2];
[0, -3; 5, -3; 7, -3; 12, -3];
[0, -4; 5, -4; 7, -4; 12, -4];
[2, -5; 3, -5; 4, -5; 8, -5; 9, -5; 10, -5];
[2, -7; 3, -7; 4, -7; 8, -7; 9, -7; 10, -7];
[0, -8; 5, -8; 7, -8; 12, -8];
[0, -9; 5, -9; 7, -9; 12, -9];
[0, -10; 5, -10; 7, -10; 12, -10];
[2, -12; 3, -12; 4, -12; 8, -12; 9, -12; 10, -12]
] |> List.collect (fun a -> a) |> List.sort
[<Fact>]
let ``when third turn is run, the pulsar returns to its initial state`` () =
pulsar |> Run.next |> Run.next |> Run.next |> List.sort |> should equal pulsar
namespace ``Examples: Spaceships``
open Xunit;
open FsUnit.Xunit;
open GameOfLife
type ``Given a glider of 5 cells`` () =
let glider = [0, -2; 1, -2; 1, 0; 2, -2; 2, -1]
[<Fact>]
let ``when fourth turn is run, the glider has moved 1 point right and 1 point down`` () =
glider |> Run.next |> Run.next |> Run.next |> Run.next |> List.sort
|> should equal (glider |> List.map (fun (x, y) -> x + 1, y - 1))
type ``Given a lightweight spaceship of 9 cells`` () =
let lwss = [0, -2; 0, 0; 1, -3; 2, -3; 3, -3; 3, 0; 4, -3; 4, -2; 4, -1]
[<Fact>]
let ``when fourth turn is run, the lightweight spaceship has moved 2 points to the right`` () =
lwss |> Run.next |> Run.next |> Run.next |> Run.next |> List.sort
|> should equal (lwss |> List.map (fun (x, y) -> x + 2, y))
Pretty neat, huh!?
comments powered by Disqus