TDD vs BDD

- select the contributor at the end of the page -
At a recent Utah Software Craftsmanship group meeting, I was asked to share my experiences using MSpec and explain how TDD is different from BDD. Since I have been using NUnit for years and MSpec since February, I was able to discuss some of the differences in the two styles of testing.

First, A Definition


TDD is Test Driven Development. This means writing a test that fails because the specified functionality doesn't exist, then writing the simplest code that can make the test pass, then refactoring to remove duplication, etc. You repeat this Red-Green-Refactor loop over and over until you have a complete feature.



BDD is Behavior Driven Development. This means creating an executable specification that fails because the feature doesn't exist, then writing the simplest code that can make the spec pass. You repeat this until a release candidate is ready to ship.

Those seem pretty similar, right? They are. The key difference is the scope. TDD is a development practice while BDD is a team methodology. In TDD, the developers write the tests while in BDD the automated specifications are created by users or testers (with developers wiring them to the code under test.) For small, co-located, developer-centric teams, TDD and BDD are effectively the same. For a much more detailed discussion, InfoQ sponsored a virtual panel on the topic.

Testing Style


So if NUnit != TDD and MSpec != BDD, what is the difference between these tools? NUnit and MSpec are 2 tools that provide for different styles of developer testing. NUnit promotes the Arrange-Act-Assert style of testing while MSpec requires the Given-When-Then (or Establish context-Because of-It should) style of testing.

Let's look at an example from the bowling game kata:
//NUnit Test
[TestFixture]
public class BowlingGameTests
{
private Game _game;

[SetUp]
public void SetUp()
{
_game = new Game();
}

[Test]
public void The_score_for_a_gutter_game_is_0()
{
RollMany(20, 0);

Assert.That(_game.Score() == 0);
}

private void RollMany(int times, int pins)
{
for (int i = 0; i < times; i++)
{
_game.Roll(pins);
}
}
}


//MSpec Test
public class With_a_game
{
Establish context = () => { _game = new Game(); };

protected static void RollMany(int times, int pins)
{
for (int i = 0; i < times; i++)
_game.Roll(pins);
}

protected static Game _game;
}

public class when_rolling_a_gutter_game : With_a_game
{
Because of = () => RollMany(20, 0);

It should_score_zero_for_the_game = () => _game.Score().ShouldEqual(0);
}


For a more detailed example, including all the tests in for the kata in both styles, please see this github repository.

How can my team do BDD?


The key to BDD is to get the specifications from the user. In other words, create tests that are not written by developers. This means tests that are not written in a programming language. These tests should be written in a language close to English (or whatever your team speaks.)

One of the oldest and best tools for this is FitNesse. The advantages of using FitNesse include

  • it facilitates thinking about features and problems in the language of business rather than the language of code

  • it requires you to focus on data in your tests

  • it can be easily included in a continuous integration environment

  • it includes a wiki for sharing information about the project

  • it requires the creation of fixtures that will help define, and refine, the API

  • it is easily shared with non-developer users


But how do I use FitNesse!?


One easy way to get started is to clone this repository and follow the instructions in the README.md.

FitNesse tests consist of 2 parts: the test pages in the wiki and the fixtures that connect the pages to the code under test. In the bddtddfitnesse repo, you will find a file FinalScore.cs in the Fixtures folder. This is the fixture used by the tests.
using System;
using System.Globalization;
using System.Linq;

namespace BowlingKata.Fixtures
{
public class FinalScore
{
private string[] _rolls;

public void Rolls(string rolls)
{
_rolls = rolls.Trim().Split(' ');
}

public string Score()
{
var game = new Game();
foreach (int roll in _rolls.Select(x => Convert.ToInt32(x)))
{
game.Roll(roll);
}
return game.Score().ToString(CultureInfo.CurrentCulture);
}
}
}

You can see that the Rolls method takes in a string and converts it to an array of rolls which are used in the Score method. The Score method uses the class under test to generate a score from the input rolls. This is then returned as a string.

To see the tests that use this fixture, navigate from the FrontPage to the SuiteBowlingGame and then to the TestScoring page. This page contains some FitNesse specific setup, some text describing the rules and then a test table.
!|final score                                  |
|rolls |score?|
|0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|0 |
|1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1|20 |
|3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3|60 |
|5 5 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0|16 |
|10 3 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |24 |
|10 10 10 10 10 10 10 10 10 10 10 10 |300 |

Each row is a separate test and the value found in the rolls column is passed into the Rolls method of the fixture. The ? on the score column tells FitNesse to query the result of the Score method on the fixture and compare it to the value in the column. When they match, the cell turns green, when they don't match, the cell turns red and the expected and actual are displayed for the user.

So TDD or BDD?


The real answer is both. You need developer tests for the fast feedback and you want user tests to ensure that the features are built to the user specs. We use FitNesse, MSpec (for unit tests), and NUnit (for UI tests) on my team at pluralsight. We try to follow the double loop described in Growing Object-Oriented Software where we write an acceptance test in FitNesse and then unit and integration tests in MSpec. Following this double loop helps us to stay focused and get features done quickly and cleanly.

http://codeobsession.blogspot.com/2012/08/tdd-vs-bdd.html

Get our content first. In your inbox.

Loading form...

If this message remains, it may be due to cookies being disabled or to an ad blocker.

Contributor

David Adsit