Thursday, March 31st 2016
Probably you've already heard a lot of good things about F# and maybe you've already tried to use it on your small or personal projects. But how should we proceed when it's a matter of something more important than a simple console application or a script. This article narrates about my personal experience of unit testing with F#.
For practical matters I've created a small project, with source code available here. It contains a small module and few unit tests for that module. That's actually this module:
module DistanceUnits open System type m type cm type inch type ft type h let mPerCm : float<m/cm> = 0.01<m/cm> let cmPerInch : float<cm/inch> = 2.54<cm/inch> let inchPerFeet: float<inch/ft> = 12.0<inch/ft> let metersToCentimeters (x: float<m>) = x / mPerCm let centimetersToInches (x: float<cm>) = x / cmPerInch let inchesToFeets (x:float<inch>) = x / inchPerFeet let centimetersToMeters: float<cm> -> float<m> = ( * ) mPerCm let inchesToCentimeters: float<inch> -> float<cm> = ( * ) cmPerInch let metersToInches: float<m> -> float<inch> = metersToCentimeters >> centimetersToInches let metersToFeets: float<m> -> float<ft> = metersToInches >> inchesToFeets let feetsToInches: float<ft> -> float<inch> = ( * ) inchPerFeet let metersToHours(m: float<m>): int<h> = raise(new InvalidOperationException("Unsupported operation"))
Theoretically you don't need any special library for test your application in F#. Although, if your are (as me) accustomed to use more standard and approved approach, you'll be happy to use one of bellow described libraries:
I'll not try to argue with you to convince that my preferred test library is the best in the world, you can use them all in similar manner. Personally, I prefer to work with xUnit and I'll use it in this article, all presented code parts could be migrated to yourTestList with ease.
Let's start by adding following packages to your project xunit and xunit.runner.visualstudio
As you know, each test framework is shipped with a minimalistic set of assertion functions. Basically such functionality is enough in 90% of cases, but in term of usage comfort it's far to be ideal. Let's take a look at few additional libraries that are handy.
Unit, so you have to cheat by writing somethings similar to
actual.Should().StartWith("S") |> ignore.
If you ever meet the necessity of mocking in your unit tests you could use Moq, but if you're looking for more F# friendly solution you'll be able to use Foq. Let's see side by side how to use those libraries.
Method call with Moq:
var mock = new Mock<IFoo>(); mock.Setup(foo => foo.DoIt(1)).Returns(true); var instance = mock.Object;
Method call with Foq:
let foo = Mock<IFoo>() .Setup(fun foo -> <@ foo.DoIt(1) @>).Returns(true) .Create()
Arguments comparison with Moq:
mock.Setup(foo => foo.DoIt(It.IsAny<int>())).Returns(true);
Arguments comparison with Foq:
mock.Setup(fun foo -> <@ foo.DoIt(any()) @>).Returns(true)
Work with properties with Moq:
mock.Setup(foo => foo.Name ).Returns("bar");
Work with properties with Foq:
mock.Setup(fun foo -> <@ foo.Name @>).Returns("bar")
In some general cases you could use the well-known "arrange phase minimizer" or fake data generator - AutoFixture. As well you can even take advantage of the integration of AutoFixture and xUnit.
If you are looking more functional approach for your tests, you can use FsCheck.
When everything is ready, you can pass to the phase of tests writing. With xUnit you can design your tests either trough standard classes or through definition of modules in F#. You're free to choose the approach that suits you best. Below I demonstrate examples of those approaches.
type ConverterTest1() = member me.``It should convert meters to centimeters as expected``() = let actual = 1100.0<cm> |> centimetersToMeters test <@ actual = 11.0<m> @> member me.``It should convert centimeters to meters as expected``() = let actual = 20.0<m> |> metersToCentimeters test <@ actual = 2000.00<cm> @>
module ConverterTest2 = open System let ``It should convert meters to feets as expected`` () = let actual = 32.0<m> |> metersToFeets test <@ actual = 104.98687664041995<ft> @> let ``It should fail when rubbish conversion is attempted`` () = raises<InvalidOperationException> <@ metersToHours 2.0<m> @
All above demonstrated tests can be successfully executed in VS or on your integration server. Hope this article was able to clarify few aspects of unit testing with F#. Thanks for reading.