Test Driven Development vs Behaviour Driven Development

by Stephen Smith on

Well-written code is useless if it doesn't do what you want it to. And even if things work at first, changes can introduce problems that are hard to spot. How do you fix this? Automated testing.

It's a simple concept, but it can be applied in a number of ways. One of the most popular is Test-Driven Development, or TDD. In it, you specify the things the program must do in order to be judged complete, write code that executes those scenarios and observes the results, and only then do you start writing the actual code those tests will be run against. This might seem a little backwards, but the benefit you get is that when you run your test suite, and every test passes, you can be assured that your program works as intended.

But there are drawbacks to this approach. For instance, specifying what your program should do is often easier said than done. And even with a fairly rigorous list of requirements, it can be difficult to translate them into the often arcane language of TDD tests. It works great once it's up and running, but getting there can be a slog.

What if there was an alternative, that allowed you to bridge the gap between business understanding and technical implementation? Turns out there is: Behaviour-Driven Development, or BDD.

With BDD, you specify your requirements in plain English (or plain French! Multiple languages can be supported, depending on your BDD tool of choice) and let your testing software take care of translating them into actions.

Let's implement a simple feature using Behat.

The Text

Feature: Basic calculations
  In order to have better productivity
  As a developer
  I need to be able to abstract away common operations

This simple language establishes four important things:

  1. What are we doing?
  2. Why are we doing it?
  3. Who is doing it?
  4. What is the result we want?

They may seem pretty obvious in this simple feature, but as you move to more complicated real-world projects, these intentions can often be murky and rest on unspoken assumptions. The point of writing them down is to force you and your fellow stakeholders to turn unspoken assumptions into explicit business rules.

Scenario: Doubling a number
  Given the number 2
  When I double it
  Then it should be 4

Again, this simple language establishes four key points:

  1. What part of our larger goal does this part accomplish?
  2. Given these preconditions
  3. When this event happens
  4. Then the result should be this

Outlining the specific scenario being tested keeps things organized, the Given-When-Then syntax gets you into the right frame of mind for thinking about testing, and most importantly, being able to write tests in plain language opens up opportunities for everybody on your team to contribute. If you need to add tests, or change them, now the people closest to the process can be the ones doing it.

The Code

(Note: Some scaffolding has been removed from this code to focus on the important parts that will change in your version. Running --init when starting your Behat project will create the necessary boilerplate, and then you can modify it.)

Here is the function under test. It is pretty simple for the purposes of this post; imagine it lives in math.php:

public function doubleNumber($number)
{
    return $number * 2;
}

And here is the setup. Imagine it lives in a different math.php file in your Behat directory (the filenames can be whatever you like, they don’t have to match):

/**
 * @Given the number :number
 */
public function setNumber($number)
{
    $this->number = $number;
}

/**
 * @When I double it
 */
public function testDoubleNumber()
{
    $this->doubledNumber = doubleNumber($this->number);
}

/**
 * @Then it should be :doubledNumber
 */
public function assertDoubledNumber($doubledNumber)
{
    if ($this->doubledNumber !== $doubledNumber) {
        throw new Exception('Number was not doubled correctly');
    }
}

How does that work?

First, note the comments: the /** means this is a “docblock” comment, a common way of adding extra information to standard PHP comments (as well as any other language that uses this style of comment, like Java or C/C++). It’s a special comment, but not necessarily a special testing comment. Software is available to parse them for a variety of uses (including, for instance, automatic documentation generation).

Next, the @ symbol, another common addition to docblock comments, usually referred to as an annotation. Behat uses annotations to let you specify which plain-text scenario line matches with which PHP function; other software use them to, for instance, ascribe authorship of a certain function to a certain person.

Note that in the annotation, the numbers have been removed, and replaced with placeholders. This would let you run the same test multiple times with different numbers, if you wanted to. As an advanced feature, you can even write regular expressions on this line for complex matching.

Finally, the functions themselves. These are the implementations of the sentences you wrote, and they either set up the context (Given), perform an action (When), or check an outcome (Then).

So if you were to run behat at your command line, it would run, in order, the setNumber, doubleNumber, and assertDoubledNumber functions, and if an Exception was raised at any point, report that test failed. Otherwise, it would report it passed.

Conclusions

Hopefully, you now understand BDD a little bit better, and can implement basic tests in PHP using Behat. There's a lot more to learn, but you're well on your way.