Aidan
Aidan

Reputation: 4891

Checking function equality in a F# unit test

I have a bunch of F# functions that implement different algorithms for the same input, kind of like the Strategy pattern. To pick the right strategy, I want to pattern match on the input argument and return the function as a value :

let equalStrategy points : seq<double> =
    ...

let multiplyStrategy factor (points: seq<double>) =
    ...

let getStrategy relationship = 
    match relationship with
        | "="  ->  equalStrategy
        | "*5" ->  multiplyStrategy 5.0
        | _    -> raise (new System.NotImplementedException(" relationship not handled"))

Now I want to write some unit tests to make sure that I return the right strategy, so I tried something like this in nUnit :

    [<TestCase("=")>]
    [<Test>]
    member self.getEqualstrategy( relationship:string ) =            
        let strategy = getStrategy relationship

        Assert.AreEqual( strategy, equalStrategy )

Now I think the code is correct and will do what I want, but the assertion fails because functions don't seem to have an equality operation defined on them. so my questions are :

(a) is there a way to compare 2 functions to see if they are the same, i.e. let isFoo bar = foo == bar, that I can use in an nUnit assertion?

or

(b) is there another unit testing framework that will do this assertion for me in F#?

Upvotes: 3

Views: 1290

Answers (3)

Tomas Petricek
Tomas Petricek

Reputation: 243041

Testing whether an F# function returned by your getStrategy is the same function as one of the funcions you defined is also essentially impossible.

To give some details - the F# compiler generates a class that inherits from FSharpFunc when you return a function as a value. More importantly, it generates a new class each time you create a function value, so you cannot compare the types of the classes.

The structure of the generated classes is something like this:

class getStrategy@7 : FSharpFunc<IEnumerable<double>, IEnumerable<double>> {
  public override IEnumerable<double> Invoke(IEnumerable<double> points) {
    // Calls the function that you're returning from 'getStrategy'
    return Test.equalStrategy(points);
  }
}

// Later - in the body of 'getStrategy':
return new getStrategy@7(); // Returns a new instance of the single-purpose class

In principle, you could use Reflection to look inside the Invoke method and find which function is called from there, but that's not going to be a reliable solution.

In practice - I think you should probably use some other simpler test to check whether the getStrategy function returned the right algorithm. If you run the returned strategy on a couple of sample inputs, that should be enough to verify that the returned algorithm is the right one and you won't be relying on implementation details (such as whether the getStrategy function just returns a named function or whether it returns a new lambda function with the same behaviour.

Alternatively, you could wrap functions in Func<_, _> delegates and use the same approach that would work in C#. However, I think that checking whether getStrategy returns a particular reference is a too detailed test that just restricts your implementation.

Upvotes: 12

Paolo Falabella
Paolo Falabella

Reputation: 25844

It would be very difficult for the F# compiler to prove formally that two functions always have the same output (given the same input). If that was possible, you could use F# to prove mathematical theorems quite trivially.

As the next best thing, for pure functions, you can verify that two functions have the same output for a large enough sample of different inputs. Tools like fscheck can help you automate this type of test. I have not used it, but I've used scalacheck that is based on the same idea (both are ports from Haskell's QuickCheck)

Upvotes: 2

Vitaliy
Vitaliy

Reputation: 2804

Functions doesn't have equality comparer:

You will have error: The type '('a -> 'a)' does not support the 'equality' constraint because it is a function type

There is a good post here

Upvotes: 2

Related Questions