Lightweight Context/Specification BDD in C#

Behavior-Driven Development (BDD) provides all of the engineering benefits of traditional Test-Driven Development (TDD) while additionally resulting in a specification that non-developers can read and validate. At its heart, BDD transforms the tests of TDD into specifications. Those specifications are expressed in English sentences that are expressed in business value as opposed to coding or engineering terms.

The most popular structure for BDD today is called the Gherkin format and follows a Given/When/Then format, like…

Given a new bowling game
When all frames are strikes
Then the score should be 300

There are frameworks like SpecFlow to help you arrange your specifications (test) into this format. However, I find this format awkward and forced. I prefer the simpler format known as Context/Specification (aka When/Should)…

When all frames are strikes
Should have a score of 300

There are frameworks, like MSpec, that attempt to make the specifications read more like English sentences. However, I find that these frameworks get in the way as much as they help. It is also nice to be able to write readable tests with just PONU (plain old NUnit). Over time, I’ve developed a convention that I find easy to write and easy to read. I’ve also developed a tool  that turns the tests into a markdown file that can be turned into a pretty HTML report.

To show the approach at work, I present some snippets from a hypothetical order pricing system I created as a “developer test” provided by a prospective employer last summer. Here is what I was given:

Instructions: Build a system that will meet the following requirements. You may make assumptions if any requirements are ambiguous or vague but you must state the assumptions in your submission.

Overview: You will be building an order calculator that will provide tax and totals. The calculator will need to account for promotions, coupons, various tax rule, etc… You may assume that the database and data-access is already developed and may mock the data-access system. No UI elements will be built for this test.

Main Business Entities:

  • Order: A set of products purchased by a customer.
  • Product: A specific item a customer may purchase.
  • Coupon: A discount for a specific product valid for a specified date range.
  • Promotion: A business wide discount on all products valid for a specified date range.

*Not all entities are listed – you may need to create additional models to complete the system.

Business Rules:

  • Tax is calculated per state as one of the following:
    • A simple percentage of the order total.
    • A flat amount per sale.
  • Products categorized as ‘Luxury Items’ are taxed at twice the normal rate in the following states
    • FL
    • NC
    • CA
  • Tax is normally calculated after applying coupons and promotional discounts. However, in the following states, the tax must be calculated prior to applying the discount:
    • FL
    • NM
    • NV
  • In CA, military members do not pay tax.

Requirements:

Adhering to the business rules stated previously:

  • The system shall calculate the total cost of an order.
  • The system shall calculate the pre-tax cost of an order.
  • The system shall calculate the tax amount of an order.

Deliverables:

  • A .NET solution (you may choose either C# or VB) containing the source code implementing the business rules.
  • Unit tests (you may choose the unit testing framework).
  • A list of assumptions made during the implementation and a relative assessment of risk associated with those assumptions.

You can see that there are quite a few specifications here. It’s a perfect scenario for a BDD approach. Lets take a look at the specification that most states charge taxes on the discounted price, while a few states require taxes to be calculated on the original price.

Here is a specification that a standard tax state calculates taxes on the discounted price…

namespace Acme.Tests.ConcerningCoupons
{
    [TestFixture]
    public class When_coupon_is_applied_to_item_on_order_in_standard_tax_state
    {
        private Order _order;
        [TestFixtureSetUp] public void Context()
        {
            Product product = new Product(10);
            Coupon coupon = CreateCoupon.For(product).WithDiscountOf(.5m);
            _order = CreateOrder.Of(product).Apply(coupon).In(StateOf.NC);
        }

        [Test] public void Should_calculate_tax_on_discounted_price()
        {
            _order.Tax.ShouldEqual(.25m);
        }
    }
}

You can see that the test fixture class name defines the context (the when) The test method name specifies the specification (the should). The Context method sets us the context in the class name. Also note the ConcerningCoupons in the namespace. This allows us to categorize the specification.

Here is the code that specifies the prediscount tax states…

namespace Acme.Tests.ConcerningCoupons
{
    [TestFixture]
    public class When_coupon_is_applied_to_item_on_order_in_prediscount_tax_state
    {
        private Order _order;
        [TestFixtureSetUp] public void Context()
        {
            Product product = new Product(10);
            Coupon coupon = CreateCoupon.For(product).WithDiscountOf(.5m);
            _order = CreateOrder.Of(product).Apply(coupon).In(StateOf.FL);
        }

        [Test] public void Should_calculate_tax_on_full_price()
        {
            _order.Tax.ShouldEqual(.50m);
        }
    }
}

Now take a look at a section of the report generated from the tests…

orders

Anyone can now compare the generated report to the original specification to verify we hit the mark. It’s a little more work to structure your tests this way, but the benefits are worth it.

The full source for the sample and the report generator are available here.

Unit Tests Just a Happy Side Effect of Test-Driven Development

Interviewing developers has given me an interesting, although not scientific, sampling of the thinking and mindset of software developers in the Atlanta area. While there have been a few bright spots, most of what I’ve learned has been disheartening.

I have found that TDD is still only an idea that most developers have heard about but never practiced. Of the ones who have practiced it, almost none of them understand what TDD was meant to achieve and only view it as automated unit testing. I believe this is due to two factors working together derail the original intent of TDD. First, TDD was named poorly. “Test” has specific meaning and has for a long time. It naturally biases us toward a certain type of thinking (strangely enough… testing and quality assurance). Second, most developers and development organizations would rather apply a formula or recipe to what they do than to take the time and effort to deeply understand what makes software development success or failure (and all of the degrees in between).

Scott Bellware is the guy who first got me thinking about a new way to approach TDD. It started with a new name: Behavior Driven Development (BDD). I actually think the “D” should be changed to Design to complete the mind shift. In a nutshell, BDD attempts to get back to the XP view of the tests being documentation of what the code should do. In this way of thinking, the set-up code is referred to as context and the assertions are referred to as specification. It has helped reform my thinking about TDD for the better.

You can catch Scott pontificating on the subject on the latest Hanselminutes podcast. Scott doesn’t get as specific as I would have liked, but this interview shows the side of Scott that I like so much. He’s passionately advocating a valuable practice and philosophy without the insults and vitriol that he is prone to fall into. I highly recommend it.

[Update: My reference to “Scott” in the above paragraph is to Bellware–not Hanselman, who is always pleasant and rarely vitriolic.]