-
Notifications
You must be signed in to change notification settings - Fork 4
Introduction
This article introduces TestFx.SpecK by:
The subject to test is a small variation of the FizzBuzz problem:
public static class FizzBuzzer
{
public static string Calculcate (int number)
{
var dividebleByThree = number % 3 == 0;
var dividebleByFive = number % 5 == 0;
if (dividebleByThree && dividebleByFive)
return "FizzBuzz";
if (dividebleByThree)
return "Fizz";
if (dividebleByFive)
return "Buzz";
return number.ToString ();
}
}
There are four branches the FizzBuzzer.Calculate
method could exit. Therefore, our test suite should contain four test cases:
- If the number is dividable by 3 and 5, return FizzBuzz
- If the number is dividable by 3 only, return Fizz
- If the number is dividable by 5 only, return Buzz
- Else, return the number as string
Whether you use NUnit, XUnit, MSpec or any other test framework, you will mostly end up following the traditional AAA pattern. As an example, the test case Dividable by 3 -> return Fizz could look like this:
// NUnit
[Test]
public void Calculate_DividableByThree ()
{
var input = 3;
var result = FizzBuzzer.Calculcate (input);
Assert.That (result, Is.EqualTo ("Fizz"));
}
// MSpec
public class when_calculating_with_number_dividable_by_three
{
static int Number;
static string Result;
Establish ctx = () => Number = 3;
Because of = () => Result = FizzBuzzer.Calculcate (Number);
It returns_Fizz = () => Result.ShouldBe ("Fizz");
}
In SpecK the test would look like this:
int Number;
FizzBuzzerSpec() // ctor
{
Specify (x => FizzBuzzer.Calculate(Number))
.Case ("Dividable by 3", _ => _
.Given ("Number is 3", x => Number = 3)
.It ("returns Fizz", x => x.Result.Should().Be("Fizz")));
}
As you see, the test is written as a single statement consisting of fluent calls. Now lets examine it line by line and find its adequate NUnit and MSpec entity:
Line | Description | In SpecK | In NUnit | In MSpec |
---|---|---|---|---|
#1 | Action |
Specify call |
Second third of method |
Because field |
#2 | TestCase |
Case call |
Test method name | Test class name |
#3 | Arrangement |
Given call |
First third of method |
Establish field |
#4 | Assertion |
It call |
Last third of method |
It fields |
At first glance, there is no obvious benefit; we have all the same constructs, but in a different order, which might cause additional cognitive effort. But how does it look like if we complete the test suite with all outstanding test cases?
[Subject (typeof (FizzBuzzer), "Calculate")]
class FizzBuzzerSpec : Spec
{
int Number;
FizzBuzzerSpec ()
{
Specify (x => FizzBuzzer.Calculate (Number))
.DefaultCase (_ => _
.Given ("Number is 1", x => Number = 1)
.It ("returns Number", x => x.Result.Should().Be("1")))
.Case ("Dividable by 3", _ => _
.Given ("Number is 3", x => Number = 3)
.It ("returns Fizz", x => x.Result.Should().Be("Fizz"))
.Case ("Dividable by 5", _ => _
.Given ("Number is 5", x => Number = 5)
.It ("returns Buzz", x => x.Result.Should().Be("Buzz"))
.Case ("Dividable by 3 and 5", _ => _
.Given ("Number is 15", x => Number = 15)
.It ("returns FizzBuzz", x => x.Result.Should().Be("FizzBuzz"));
}
}
This example shows the method-oriented approach of SpecK. The action is only stated once and is followed by multiple test cases; each test case consisting of a declarative name and a set of arrangements and assertions. Compared to the complete test suites for NUnit and MSpec, SpecK reduces the total lines of code by 66% when having blank lines for AAA separation (40% when removing blank lines).