Thursday, May 12, 2016

Excel calculations in F# - Functional Testing

We’ve now seen the guts of a codebase that does Excel calculations. On it’s own it isn’t too revealing to look at algorithms and functions, so I thought it wise to show some of the tests that I wrote.

I’ll start by referencing the Test Pyramid:

Test Pyramid

The long and short of it is that we want to write more Unit tests than Component tests, more Component tests than Integration tests, and so on.


The Test Pyramid and Functional Programming

I’ve found functional programming to fit quite nicely into this paradigm — its emphasis on composition and pipelining means that a Component is quite often made up of a handful of composed Units, with multiple Components integrated together by composition once more. By testing each unit, we then feel sufficiently comfortable that we almost only need to smoke-test the component.

That is, if we have something like

let function1 = ...
let function2 = ...
let function3 = ...

let composite = function1 >> function2 >> function 3

and we thoroughly test function1, function2 and function3, we barely need to test composite. Granted, this security is partly due to the fantastic compile-time type system that F# gives us — the mere fact that the code above compiles is a reasonably strong indicator that the behaviour of composite is correct!

Having seen how good functional architecture leads us naturally on to good testing practices, let’s talk about the available testing tools in F#.


NUnit, NCrunch, and Unquote

Fans of C# might look at the above and think: well I know what the first two are, they are a formidable combination for continuous testing in Visual Studio, but how do they work in F#, and what is Unquote?

Taking a step back, if you are a .NET developer and don’t use NCrunch, start using it now!! Even if you aren’t doing Test-Driven Development, it will give you the confidence to know which bits of your code are tested, which aren’t, and whether your last one-line chance has broken anything. In terms of F# support, well, it just works like it does with C#!

The new boy on the block, Unquote, is pretty cool too. It uses two really nice features of F# that we haven’t looked a much yet, Code Quotations and Custom Operators. The resulting test syntax is beautiful and very readable — more that can be said about things like NUnit Assertions.

The easiest way to see it in action (other than trying it yourself) is to look at some real tests. Casting your mind back to the first bits of code we looked at last time, we had this function called StringToTokenList that took the contents of an Excel formula and turned it into a list of Token items that were strongly typed.

Here’s the first test for that function:

[<Test>]
let ``StringToTokenList 1+2``() = 
    StringToTokenList "1+2" =! [ Val(float 1)
                                 Operator Operator.Add
                                 Val(float 2) ]

A couple of nice things here are that:

  • We don’t have to bother with the tedious Arrange/Act/Assert pattern that pervades object-oriented tests. We simply write what we want to be taken as true, and use Unquote’s equality operator =!.
  • We can name the tests with as much verbosity as required, thanks to F#’s double-backtick notation. I’m not sure I am using this to full potential yet — maybe I’m stuck in the C# way of terse test names!
  • In this simple example I didn’t use a code quotation, but if the test setup were more complex I could do that rather than use a custom equality operator.
  • It’s a minor thing, but being able to initialise arrays using separate lines and no semi-colons is handy.

It carries on and gets more complicated — we can include whitespace, brackets, references, etc. in the expression, but the tests look exactly the same:

[<Test>]
let ``StringToTokenList multi digit integers``() = 
    StringToTokenList "( 123 + 321 )" =! [ LeftBracket
                                           Val(float 123)
                                           Operator Operator.Add
                                           Val(float 321)
                                           RightBracket ]

[<Test>]
let ``StringToTokenList A1 + A2 * BB312``() = 
    StringToTokenList "A1 + A2 * BB312" =! [ Ref { col = 1
                                                   row = 1 }
                                             Operator Operator.Add
                                             Ref { col = 1
                                                   row = 2 }
                                             Operator Operator.Multiply
                                             Ref { col = 54
                                                   row = 312 } ]

I’ve introduced you to the tools and frameworks I’ll use for testing, and hopefully you have some idea of what a Token list looks like, so let’s look at some more of the code tests before we move on to the working system!


Testing the Operators, Parser, and Calculator

I said last time that I had made the operators work on lists as well as numbers, and that it would be easy to extend this functionality to other types.

To test the Shunting-Yard algorithm, I was slightly naughty and used the example on the Wikipedia page. Here’s what my test looked like:

[<Test>]
let ``InfixToPostfix 5 + ((1 + 2) * 4) - 3``() = 
    InfixToPostfix [ Val (float 5)
                     Operator Operator.Add
                     LeftBracket
                     LeftBracket
                     Val (float 1)
                     Operator Operator.Add
                     Val (float 2)
                     RightBracket
                     Operator Operator.Multiply
                     Val (float 4)
                     RightBracket
                     Operator Operator.Subtract
                     Val (float 3) ]
    =! PostFixList([ Val (float 5)
                     Val (float 1)
                     Val (float 2)
                     Operator Operator.Add
                     Val (float 4)
                     Operator Operator.Multiply
                     Operator Operator.Add
                     Val (float 3)
                     Operator Operator.Subtract ])

Believe it or not, this one test managed to root out enough issue with my original implementation of the algorithm that all the subsequent tests passed first time — I guess that’s why it’s on the Wikipedia page!

For the raw operator functions such as add, I briefly experiemented with Property-Based Testing, but to be honest I struggled to grasp the nuances straight away. The code for those tests is here, and whislt it works for things like addition for which we have a good understanding of the required properties, I couldn’t easily translate it to higher-level concepts such as operator precedence.

The ‘normal’ tests I wrote were pretty basic, e.g.

[<Test>]
let ``Add x y works on values``() = Add (Val 1.0) (Val 2.0) =! Val 3.0

The calculator tests were slightly more interesting (but not much). Taking the previous example used to test the Shunting-Yard algorithm, we have

[<Test>]
let ``Calculate 5 + ((1 + 2) * 4) - 3 = 14``() = 
    Calculate(PostFixList([ Val(float 5)
                            Val(float 1)
                            Val(float 2)
                            Operator Operator.Add
                            Val(float 4)
                            Operator Operator.Multiply
                            Operator Operator.Add
                            Val(float 3)
                            Operator Operator.Subtract ]))
    =! ValResult(float 14)

Pretty basic tests — the idea of showing you some of them was to give a more intuitive feel of what the output code looked like.


System Tests: using the functions in Excel!

Excel Screenshot

The screenshot above shows a few examples of our calculator replicating Excel functionality, which serves to show that it does roughly what it should! The bottom example shows a vector calculation

RA Screenshot

This next shot shows something a bit more complex. If you look at the formula bar, you will see that it’s doing more than just arithmetic with numbers. It’s taking a reference to a cell containing 10,000 Monte Carlo simulations, performing a calculation on them to account for changes in exchange rates, and saving the results (10,000 new simulations) in a single cell.

Whilst this is very much a prototype, I think it’s a long way towards solving the problem that I originally wanted to solve. Sure, it needs a bit more TLC to be race-ready, but I think I’ve learned a decent amount about both Excel and F# in the process!


Next Time

Something a little different from the usual. I’ll be talking about the SOLID prinicples of object-oriented design and how they are much more compatible with functional programming that first meets the eye.

No comments:

Post a Comment