Making your own simple test framework in F#

I’m currently programming in F# under Linux. This means no Visual Studio and no MSTest GUI test runner. My first reaction was to start looking at using NUnit or xUnit for my project.

I then had a thought. Why not write my own simple test “framework”. It sounded like something easy to do and pretty fun. In retrospect, it was.

Of course this is nothing like a full featured test framework but it’s enough for my purposes.

Here is the test framework:

module TestFramework

// assert functions
let assertAreEqual expected actual =
    if expected <> actual then
        sprintf "Test failed, expected %A, actual %A" expected actual
    else
        "Test passed"

let assertIsGreaterThan target actual =
     if target >= actual then
        sprintf "Test failed, expected %A to be greater than %A" target actual
     else
        "Test passed" 

// test runner
let runSingleTest (testName, testFunction) =
    sprintf "%s... %s" testName (testFunction())       

let runTests testList =
    testList |> List.map (runSingleTest)

let consoleTestRunner testList =
    runTests testList |> List.iter (printfn "%s")
    printfn "%s" "Ran all tests."

F#’s automatic generalization cuts down on explicit generics or function overloads in the assert functions.

This makes the code more succinct and development less tedious.

Right now there is only a consoleTestRunner but more types of test runners could be implemented to accommodate other interfaces like a GUI.

And here are some sample tests:

module Tests

open TestFramework

// modules under test
open HeightMap
open MidpointDisplacement

// tests included in run
let testsToRun =
    [
        "newHeightMap will return a 0 initialized height map",
        fun() ->
            let hm = newHeightMap 5
            let result = hm.Map |> Array.sum
            assertAreEqual 0.0 result;

        "set will update height map",
        fun() ->
            let hm = newHeightMap 5
            hm.Set 1 1 0.5
            assertAreEqual 0.5 (hm.Get 1 1);

        "convertFloatToRgb will convert 0.0 to r:0, g:0, b:0",
        fun() ->
            let red, green, blue = convertFloatToRgb 0.0
            assertAreEqual (0, 0, 0) (red, green, blue);

        "convertFloatToRgb will convert 1.0 to r:255, g:255, b:255",
        fun() ->
            let red, green, blue = convertFloatToRgb 1.0
            assertAreEqual (255, 255, 255) (red, green, blue);                                               

        "convertFloatToRgb will convert 0.5 to r:127, g:127, b:127",
        fun() ->
            let red, green, blue = convertFloatToRgb 0.5
            assertAreEqual (127, 127, 127) (red, green, blue);

        "middle will set the midpoint value between each corner to the average of the corners plus the result of a function",
        fun() ->
            let variationFunction x = x + 0.1
            let hm = newHeightMap 2
            hm.Set 0 0 0.5
            hm.Set 0 4 0.5
            hm.Set 4 0 1.0
            hm.Set 4 4 0.25
            middle hm (0, 0) (4, 0) (0, 4) (4, 4) variationFunction
            let middleValues = [hm.Get 0 2; hm.Get 2 0; hm.Get 2 4; hm.Get 4 2]
            assertAreEqual [0.6; 0.85; 0.475; 0.725] middleValues;
    ]

The tests themselves are represented as data, which is a common concept in functional programming. Each test is a tuple with the first argument being the name of the test as it will appear in the console during a test run.

Here is how to call the framework along with a sample test run output :

open TestFramework
open Tests

consoleTestRunner testsToRun

newHeightMap will return a 0 initialized height map… Test passed
set will update height map… Test passed
init corners will assign values to the height map corners… Test passed
convertFloatToRgb will convert 0.0 to r:0, g:0, b:0… Test passed
convertFloatToRgb will convert 1.0 to r:255, g:255, b:255… Test passed
convertFloatToRgb will convert 0.5 to r:127, g:127, b:127… Test passed
set will correctly change the values of an heightmap… Test passed
middle will set the midpoint value between each corner to the average of the corners plus the result of a function… Test passed
Ran all tests.

Since I’m just beginning with F#, I had my code reviewed on codereview.stackexchange.com and had some great answers. I can’t recommend this site enough for when you are starting out with a new language.

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