Updated on November 16, 2022
By David Adsit

TDD vs BDD: What's the difference?

Learn about the differences between Test Driven Development and Behavior Driven Development. 

At a 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 2012, I was able to discuss some of the differences in the two styles of testing.

TDD vs BDD: Definitions

Let's start by defining each of the key terms and discussing the major distinctions between them. 

What does TDD mean?

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.

What does BDD mean?

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.

What's the difference between TDD and BDD?

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.

TDD vs BDD: Testing Styles

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.

NUnit 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 Testing

Now here's an example of an MSpec test:

//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 for the kata in both styles, please see this github repository.

How do BDD tests work?

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.)

BDD Testing Tools

Some BDD testing tools include Zephyr scale, Concordian, and FitNesse. As an example, let’s look at a how FitNesse works.  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

How do I use FitNesse for BDD Testing?

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.

My team follows 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.

David Adsit

David Adsit is software engineer and former Lead Architect at Pluralsight with over twenty years of experience.