The Unit Testing Story in F# Revisited
Running the Tests Again
Now, I'm able to run my functions just as before and the runner will now recognize them. Below is just a simple example of some unit tests to determine whether numbers are prime or not. I'm extending the System.Int32 to add a property to the instance to determine whether it is prime. Just to prove a point on how flexible F# really is, I'm also able to extend the Int32 class using static methods, something that you cannot do with C# and extension methods. More and more, I love the language itself and finding myself trapped sometimes by the limits of C#. But, that's sidetracking, so let's get to the unit tests.
#light
#R @"D:\Tools\xunit-1.0.1\xunit.dll"
open Xunit
let isPrimeNumber(i) =
let limit = int(sqrt(float(i)))
let rec check j =
j > limit or (i % j <> 0 && check(j + 1))
check 2
type System.Int32 with
member i.IsPrime
with get () = isPrimeNumber(i)
static member IsPrimeNumber(x) =
isPrimeNumber(x)
[<Fact>]
let IsPrime_WithPrimeNumber_ShouldReturnTrue() =
Assert.True((7).IsPrime)
[<Fact>]
let IsPrime_WithNonPrimeNumber_ShouldReturnFalse() =
Assert.False((21).IsPrime)
Assert.False(System.Int32.IsPrimeNumber(45))
And then when I run it through the GUI runner, I sure enough get two passing tests. It was asked of me last week at the Philly ALT.NET meeting about TDD with F# and I see no problem with this at all, and in fact I actively encourage it. But, you have to think about this in a different light when talking about objects and behaviors, and then turning around to functions and behaviors.
Getting Going with Gallio
As I mentioned last time, Jeff Brown has been hard at work to support the F# community as well. I was able to get the right build going of Gallio finally after there may have been some mixups with getting the latest code. Anyhow, I am now able to get these same tests to work, but using MbUnit version 3 and through the Gallio Icarus Runner. If you're not familiar with Gallio, it is an open platform of tools and runners that is extensible to all testing frameworks. Jeff Brown talked about it on Hanselminutes with Brad Wilson of xUnit.net fame, Roy Osherove and Charlie Poole of NUnit on the Past, Present and Future of Unit Testing Frameworks.
So, let's just modify the above code to migrate to Gallio with MbUnit version 3 and see how we do:
#light
#R @"D:\Program Files\Gallio\bin\Gallio.dll"
#R @"D:\Program Files\Gallio\bin\MbUnit.dll"
open MbUnit.Framework
let isPrimeNumber(i) =
let limit = int(sqrt(float(i)))
let rec check j =
j > limit or (i % j <> 0 && check(j + 1))
check 2
type System.Int32 with
member i.IsPrime
with get () = isPrimeNumber(i)
static member IsPrimeNumber(x) =
isPrimeNumber(x)
[<Test>]
let IsPrime_WithPrimeNumber_ShouldReturnTrue() =
Assert.IsTrue((7).IsPrime)
[<Test>]
let IsPrime_WithNonPrimeNumber_ShouldReturn_False() =
Assert.IsFalse((21).IsPrime)
Assert.IsFalse(System.Int32.IsPrimeNumber(45))
And we can notice through the Gallio runner that it's only detecting the MbUnit tests right now, unfortunately. Hopefully that issue will be resolved soon.
BDD Specs in F#?
F# is a pretty flexible language for unit testing and even BDD style. I wonder if we could take some lessons from the spec BDD framework for Scala and apply to F#. Just a thought...
If you're not familiar with specs, it's a BDD framework with some interesting syntax that I'm still coming to terms with. But the concept looks interesting. Take a look at a quick example and see if it speaks to you.
package podwysocki.specs
object scalaSpecExample extends Specification {
"A hello world spec" should {
"return something" in {
"hello" mustBe "hello"
}
}
}
As I've played around with Scala, this is a pretty interesting concept. I'm much more a fan of F# as a language, but still there are some interesting pieces to Scala. I'm also interested in using MSpec from Aaron Jensen at some point, but have a bit on my plate and other points of focus right now.
Wrapping It Up
In the mean time, we have another testing framework to consider. Me, personally, I prefer xUnit.net because of the functional aspects of Assert.Throws and so on. But, that option is up to you quite frankly. There is a good story to be told here with regards to unit testing and F# that is not to be overlooked.