Introducing Penfold

I’ve convinced myself that .NET needs a new behaviour driven development library. Here are my simple demands:

  1. Specification statements that can be written with spaces, punctuation and, most importantly, emojis 😜.

  2. Nested contexts that can group assertions into logical collections and share variables, setup code and scope.

  3. Support for different styles of specification, or even the ability to roll your own specification DSL.

  4. The option to enhance the textual output of the specification with extra information and comments.

Now I know that RSpec, Jasmine and Mocha tick all of these boxes, but I haven’t found a satisfactory alternative for .NET. So I’ve had a crack at writing one - It’s called Penfold and is available now on Nuget.

[TestFixture]
public class CalculatorSpecification : Specification
{
    public CalculatorSpecification()
    {
        var calculator = new Calculator();

        before_each = () => calculator.Clear();

        describe["Addition"] = () =>
        {
            context["adding two and three"] = () =>
            {
                before = () => calculator.Key(2).Add(3);

                it["sets the total to five"] = () =>
                {
                    calculator.Total.ShouldEqual(5);
                };

                it["sets the equation history to:"] = () =>
                {
                    log(calculator.Equation.ShouldEqual("2 + 3"));
                };
            };

            context["adding two, three and four"] = () =>
            {
                before = () => calculator.Key(2).Add(3).Add(4);

                it["sets the total to nine"] = () =>
                {
                    calculator.Total.ShouldEqual(9);
                };

                it["sets the equation history to:"] = () =>
                {
                    log(calculator.Equation.ShouldEqual("2 + 3 + 4"));
                };
            };
        };
    }
}

which outputs the following to the console:

CalculatorSpecification
  Addition
    adding two and three
      sets the total to five
      sets the equation history to:
        2 + 3
    adding two, three and four
      sets the total to nine
      sets the equation history to:
        2 + 3 + 4

Note: if you check out my calculator implementation, you’ll see that I’m not expecting to be head-hunted by Casio any time soon!

The Basics

Penfold specifications inherit from a Specification base class and are written in the default constructor. Each specification must have at least one assertion to be executed, so the bare minimum would be:

[TestFixture]
public class CalculatorSpecification : Specification
{
    public CalculatorSpecification()
    {
        it["can add two numbers together"] = () =>
        {
            new Calculator().Key(1).Add(2).Total.ShouldEqual(3);
        };
    }
}

You’ll probably recognize the [TestFixture] annotation - that’s because Penfold is built on top of good old NUnit so in theory you can use any test runner that supports NUnit[1].

Penfold doesn’t include an assertion library, all of the code in this article uses my favourite - Machine.Specifications.Should, but you can use any assertion framework you like.

Assertions can be grouped together inside a context where they share variables and setup code. Contexts are defined by using the describe and context keywords:

describe["Addition"] = () =>
{
    var calculator = new Calculator();

    context["adding two and three"] = () =>
    {
        before = () => calculator.Key(2).Add(3);

        it["sets the total to five"] = () =>
        {
            calculator.Total.ShouldEqual(5);
        };

        it["sets the equation history to '2 + 3'"] = () =>
        {
            calculator.Equation.ShouldEqual("2 + 3");
        };
    };

    context["adding two, three and four"] = () =>
    {
        ...

The calculator variable is scoped to the “Addition” context, it can be accessed from within the nested test steps but is not available outside of the describe block.

Gherkin Syntax

Penfold also supports Gherkin syntax:

[TestFixture]
public class CalculatorFeature : Specification
{
    public CalculatorFeature()
    {
        var calculator = new Calculator();

        comment = @"
            as a math idiot
            I want to use a calculator
            so I don't make mistakes with simple arithmetic
        ";

        Scenario["Addition"] = () =>
        {
            Given["I have pressed clear"]    = () => calculator.Clear();
            When["I key in two"]             = () => calculator.Key(2);
            When["I add three"]              = () => calculator.Add(3);
            Then["the total is five"]        = () => calculator.Total.ShouldEqual(5);
            Then["the equation history is:"] = () => log(calculator.Equation.ShouldEqual("2 + 3"));
        };

        Scenario["Division"] = () =>
        {
            Given["I have pressed clear"]    = () => calculator.Clear();
            When["I key in twelve"]          = () => calculator.Key(12);
            When["I divide by four"]         = () => calculator.Divide(4);
            Then["the total is three"]       = () => calculator.Total.ShouldEqual(3);
            Then["the equation history is:"] = () => log(calculator.Equation.ShouldEqual("12 / 4"));
        };
    }
}

which outputs the following to the console:

CalculatorFeature
  as a math idiot
  I want to use a calculator
  so I don't make mistakes with simple arithmetic
  Scenario: Addition
    Given I have pressed clear
    When I key in two
    When I add three
    Then the total is five
    Then the equation history is:
      2 + 3
  Scenario: Division
    Given I have pressed clear
    When I key in twelve
    When I divide by four
    Then the total is three
    Then the equation history is:
      12 / 4

In the example above I’ve used inline lambdas for the step definitions to aid readability, but you can still use block lambdas for multiple lines of code.

Penfold actually makes it really easy to specify your own keywords, but that’s a blog for a different day. If you’re interested, checkout the Specification.cs source code to see how the standard keywords are defined.

Logging

Sometimes you end up repeating yourself between the specification statement and the code. For example:

it["sets the equation history to '2 + 3'"] = () =>
{
    calculator.Equation.ShouldEqual("2 + 3");
};

The log method allows you to enrich the specification output from within the code, so the example above could be rewritten as:

it["sets the equation history to:"] = () =>
{
    var expected = "2 + 3";
    log(expected);
    calculator.Equation.ShouldEqual(expected);
};

which results in the following output:

sets the equation history to:
  2 + 3

One of the cool features of Machine.Specifications.Should is that the ShouldEqual extension returns the expected string, so we can rewrite this as a one-liner:

it["sets the equation history to:"] = () =>
{
    log(calculator.Equation.ShouldEqual("2 + 3"));
};

Ignored, Pending & Categorization

You can ignore a step by using the x or @ignore keywords. Individual steps can be ignored, or you can ignore an entire context.

x = it["I'm being ignored"] = () =>
{
    ...
};

@ignore = it["so am I!"] = () =>
{
    ...
};

Steps which haven’t been implemented can be marked as pending by setting their action to null:

it["I'm not implemented yet"] = null;

This flags the step as inconclusive to the test runner and outputs:

I'm not implemented yet [PENDING]

Finally, categories can be added by using the following syntax:

@_["Category A"] =
@_["Category B"] =
it["does something"] = () =>
{
    ...
};

Categories can be applied to individual assertions, or to an entire context.

Expecting Exceptions

You can use the Catch method as an easy way to catch an exception and return it:

context["dividing by zero"] = () =>
{
    it["explodes 💣💥☠️"] = () =>
    {
        Catch(() => calculator.Key(2).Divide(0))
            .ShouldBeOfExactType<DivideByZeroException>();
    };
};

I told you there’d be emojis!

Summary

This started out as a bit of an experiment, but I’m quite happy with the result. I plan to try it on a couple of side projects and see how it goes.

One minor niggle is that Visual Studio doesn’t support code folding on the lambda expressions. Once a specification gets long enough, it’s useful to be able to fold away certain contexts that you aren’t interested in. I’ve found the C# outline extension which sorts this out.

If you decide to use Penfold on your own project, please give the repo a star on GitHub so that I know it’s being used.

Oh and why call it Penfold? Because unlike Danger Mouse, he needs specs!


[1] I’ve had some difficulty with TestDriven.net, it can’t identify the tests because they are specified inside the class constructor. I’ve developed an adapter that allows the specification to be executed, but it can only execute the whole specification and not individual assertions or contexts.