4

I am trying to set up a test suite for an F# project using NUnit. It seems that especially when testing things like parsers and type checkers one typically has a list of valid input data and a list of invalid data. The tests itself are practically identical, so I am looking for a clever way to avoid writing a test function for each and every data item and instead seperate the test function from the data. Apparently, there seems to be something called test cases for that but I am having a hard time to find comprehensive documentation for the usage of NUnit 3 with F# in general and specifically a best practice example for my scenario.

Any pointers and hints are greately appreaciated!

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
Friedrich Gretz
  • 535
  • 4
  • 14

3 Answers3

6

This is an updated answer for NUnit 3.x since my original answer showed a NUnit 2.x example.

The examples below are not meant to be comprehensive, but enough to get you over the threshold and up an running. The things of note are that the test functions are written using argument list instead of currying. Also there are several ways to generate test data using NUnit 3.x attributes, e.g. Pairwise, but sadly none of the attributes available know how to generate test data for discriminated unions.

Also FSUnit is not needed and I didn't try to make it work as the difference between NUnint 2.x and 3.x are so dramatic that I was happy just to get the following examples working. Maybe in the future I will update this answer.

namespace NUnit3Demo

open NUnit.Framework

module MyTest = 
    // ----------------------------------------------------------------------

    [<Pairwise>]
    let pairWiseTest([<Values("a", "b", "c")>] (a : string), [<Values("+", "-")>] (b :string), [<Values("x", "y")>] (c : string))
        = printfn "%A %A %A" a b c

    // ----------------------------------------------------------------------

    let divideCases1 =
        [
            12, 3, 4
            12, 2, 6
            12, 4, 3
        ] |> List.map (fun (q, n, d) -> TestCaseData(q,n,d))

    [<TestCaseSource("divideCases1")>]
    let caseSourceTest1(q:int, n:int, d:int) =
        Assert.AreEqual( q, n / d )

    // ----------------------------------------------------------------------

    let divideCases2 =
        seq {
            yield (12, 3, 4)
            yield (12, 2, 6)
            yield (12, 4, 3)
        }

    [<TestCaseSource("divideCases2")>]
    let caseSourceTest2(q:int, n:int, d:int) =
        Assert.AreEqual( q, n / d )

    // ----------------------------------------------------------------------

    [<TestCase(12,3,4)>]
    [<TestCase(12,2,6)>]
    [<TestCase(12,4,3)>]
    let testCaseTest(q:int, n:int, d:int) =
        Assert.AreEqual( q, n / d )

    // ----------------------------------------------------------------------

    let evenNumbers : int [] = [| 2; 4; 6; 8 |]

    [<TestCaseSource("evenNumbers")>]
    let caseSourceTest3 (num : int) =
        Assert.IsTrue(num % 2 = 0)

Leaving Original Answer since it was noted in other answer by OP.

The following example was written 3 years ago using NUnit 2.x so it is a bit dated but should give you an ideal.

You create an array of the test data, then index into the array to pull out the test values and expected results. The nice thing about this is that you don't wind up writing lots of individual test for a function.

This comes from a project some of us did years ago.

open NUnit.Framework
open FsUnit

let private filterValues : (int list * int list)[] = [| 
    (
        // idx 0
        // lib.filter.001
        [],
        []
    ); 
    (
        // idx 1
        // lib.filter.002
        [-2],
        [-2]
    );
    (
        // idx 2
        // lib.filter.003
        [-1],
        []
    );
    (
        // idx 3
        // lib.filter.004
        [0],
        [0]
    );
    (
        // idx 4
        // lib.filter.005
        [1],
        []
    );
    (
        // idx 5
        // lib.filter.006
        [1; 2],
        [2]
    );
    (
        // idx 6
        // lib.filter.007
        [1; 3],
        []
    );
    (
        // idx 7
        // lib.filter.008
        [2; 3],
        [2]
    );
    (
        // idx 8
        // lib.filter.009
        [1; 2; 3],
        [2]
    );
    (
        // idx 9
        // lib.filter.010
        [2; 3; 4],
        [2; 4]
    );
    |]

[<Test>]
[<TestCase(0, TestName = "lib.filter.01")>]
[<TestCase(1, TestName = "lib.filter.02")>]
[<TestCase(2, TestName = "lib.filter.03")>]
[<TestCase(3, TestName = "lib.filter.04")>]
[<TestCase(4, TestName = "lib.filter.05")>]
[<TestCase(5, TestName = "lib.filter.06")>]
[<TestCase(6, TestName = "lib.filter.07")>]
[<TestCase(7, TestName = "lib.filter.08")>]
[<TestCase(8, TestName = "lib.filter.09")>]
[<TestCase(9, TestName = "lib.filter.10")>]
let ``List filter`` idx = 
    let (list, _) = filterValues.[idx]
    let (_, result) = filterValues.[idx]
    List.filter (fun x -> x % 2 = 0) list 
    |> should equal result
    filter (fun x -> x % 2 = 0) list 
    |> should equal result

IIRC the problem with using NUnit with F# is to remember to use <> in the right location.

PhilT
  • 4,166
  • 1
  • 36
  • 26
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
  • However, I wonder if I could avoid the noise of spelling out a line for each entry in the array. Imagine I have an array with 100 entries... this becomes quite an overhead to add all those lines and keep them in sync as the array of test inputs grows or shrinks. I was wondering if this could be somehow done implicitly. – Friedrich Gretz Mar 02 '16 at 10:24
  • @FriedrichGretz Jack who worked on the project with me had the same idea and started to work on it. I don't see it in his GitHub page anymore. You could write a program to take data from a text file and use that to generate the code since it pretty much boilerplate. – Guy Coder Mar 02 '16 at 10:40
  • One approach I started on for a C++ compiler but didn't finish was to use Prolog's [DCG](https://en.wikipedia.org/wiki/Definite_clause_grammar) to generate a subset of the cases and have it generate the test code. I say subset because of the combinatorial explosion. The beauty of DCG is that running it with bound vraibles, e.g. input, acts as a translator/compiler and running it with free variables can generate test cases. Thus you get a set of test cases and a second compiler as verification. Also adding a new clause is easy and then it generates the new test cases easily. – Guy Coder Mar 02 '16 at 10:43
  • I am not suggesting you use DCG, but if you do a lot of test case generation or compiler work it might be worth a look, but learning Prolog is not intuitive, one must thing naturally in recursion and logic. – Guy Coder Mar 02 '16 at 10:48
  • In the first example the test data using `seq` doesn't appear to work. I get `Not enough arguments provided, provide at least 3` – PhilT Feb 19 '17 at 12:36
  • @PhilT `These are examples that were specific to the question`. The OP is not trying to use NUnit in the standard manner. Also these were never written to work with `seq`, they work with `List`. I would offer more help but I haven't done this in awhile so am rusty on it. The main point is that if you are moving from NUnit 2.x to NUnit 3.x, you are better off forgetting everything you know about NUnit 2.x and start from scratch by reading all of the documentation and build up examples. – Guy Coder Feb 19 '17 at 12:56
  • No problem. There are not too many examples of using test data in F# so just trying to improve these to help other newcomers to F# like myself. – PhilT Feb 21 '17 at 09:15
5

In NUnit3 there is TestCaseSource and TestCaseData and for the best practices part I added FsUnit:

namespace NUnit3Demo

open NUnit.Framework
open FsUnit

[<TestFixture>]
module MyTest = 

    let methodToBeTested s = 
        if String.length s > 3 then failwith "Something's wrong"
        else String.length s

    let validData =
        [
            TestCaseData("   ").Returns(3)
            TestCaseData("").Returns(0)
            TestCaseData("a").Returns(1)
        ]

    let invalidData =
        [
            "    "
            "abcd"
            "whatever"
        ]

    let otherInvalidData =
        [
            "just"
            "because"
        ]

    [<TestCaseSource("invalidData");
      TestCaseSource("otherInvalidData")>]
    let ``More than 3 characters throws`` s = 
        (fun () -> methodToBeTested s |> ignore)
        |> should throw typeof<System.Exception>

    [<TestCaseSource("validData")>]
    let ``Less than 4 characters returns length`` s = 
        methodToBeTested s

Note that TestCaseData can take and return arbitrary objects (obviously they should match the test signatures). Also, the data can be written even nicer:

let validData =
    [
        "   ", 3
        "",    0
        "a",   1
    ] |> List.map (fun (d, r) -> TestCaseData(d).Returns r)
CaringDev
  • 8,391
  • 1
  • 24
  • 43
  • 1
    Thank you for the example and the reference to the NUnit doc. This is precisely what I was looking for originally. And I think I am starting to understand how to read my way through the NUnit Wiki and map their examples into F#. – Friedrich Gretz Mar 03 '16 at 13:41
  • 1
    I also liked the second snippet where you map those string tuples into a list of TestCaseData. Really cool example of cleaner code and the fluent interface of TestCaseData! – Friedrich Gretz Mar 03 '16 at 13:47
  • 1
    You don't need to use `static member` everywhere if you define the tests in a module instead of a type. E.g. `module MyTest =`. You can also dispense with the additional `[]` attribute if you're using []` or []` – PhilT Feb 19 '17 at 12:14
  • @PhilT Thanks. Good to know that NUnit3 finally supports static classes (personally I switched to xUnit). I updated the answer accordingly. – CaringDev Feb 19 '17 at 17:54
0

By the end of the day, I realised I should not have used an array in the first place!

I finally understood how the TestCase mechanism is supposed to work: it simply passes the annotation's contents as a parameter to a function that now is not unit->unit anymore but (in my case) string->unit. Thus all my data items are now pasted into individual TestCase annotations, and the array is gone. Surely this might look a bit odd, to have TestCase annotations whose contents span many lines of code but the array was not beautiful either, so be it.

Unfortunately, my solution is not universally applicable, e.g. would not work for Guy Coder's code above. The reason is indicated here: https://stackoverflow.com/a/28015585/2289899 where they say:

CLI has a restriction regarding kinds of attribute parameters:

  • primitive: bool, int, float, etc
  • enums
  • strings
  • type references: System.Type
  • 'kinda objects': boxed (if needed) representation of types from above
  • one dimensional array of one of types from above (ie. no nested arrays allowed)

So we can conclude at this point that you can not use tuple as an attribute parameter's type.

Community
  • 1
  • 1
Friedrich Gretz
  • 535
  • 4
  • 14