Source

blog / source / en_posts / testing_python / tdd-bdd.rst

Full commit

TDD and BDD

Note

This page is a part of :doc:`../2011-03-09-testing-python` post.

Now I want to go away from practice to theory again and talk a little about two things: TDD and BDD. TDD will go first.

TDD

The most common mistake in the testing world is to mess TDD and (unit-) testing in general. TDD is not just unit-testing, it's much more. TDD is a way to formalize process of developing software into something that more seems like a compiler-driven development, where instead of compiler trying to figure out errors as you develop small pieces, you write tests to figure them out.

Robert Martin defines 3 rules of TDD as this:

  • You are not allowed to write any production code unless it is to make a failing unit test pass.
  • You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
  • You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

As you can see, if your whole team of programmers would work this way, your codebase would keep being stable all the time, and it would evolve little by little, no matter how complex the tasks are.

From my observations, when you work with lots of "state" or when you need to prototype as you develop, TDD is hard to adopt, and it's sometimes even becomes absurd to see how people transform TDD into "almost-TDD" or "better-TDD" by weaken some of TDD rules (or adding exceptions). I actually did that too. And now I see that in places like "best software practices.ppt" inside my company.

I believe you shouldn't add any exceptions in these rules of TDD or try to make it "more real-world from your perspective". Programmers are smart enough to understand if it's not fit for they're tasks or if they want to add some exceptions into these rules.

BDD

Different idea of upgrading testing experience is Behaviour-Driven Development. The main idea is that you need to keep constantly focus on what you test and why you should do that, as a result Dan North created a JBehave framework, which was the same as JUnit but with test methods called with prefix should_* instead of test_*. BDD has whole filosophy behind that, you can go and read wikipedia and other things about BDD (for example, frameworks that let you describe your tests as plain text).

As lesson from that super-idea, I now call all my tests with prefix test_should_ (as you have probably already saw). That really helps focusing on what test does at time of test-naming. Of course, BDD is not just about naming your tests, but the only visible part is prefix one (at my tests). I am still investigating frameworks that let you describe test as text, but didn't have experience with that.

Ping-pong programming

As a bonus, there's a technique called "ping-pong programming" when one person writes a test, and another implements it. Then they switch.

Writing test from action to mocks

To write tests, you start from it's name (and focus on what it should do), like:

def test_should_sort_by_name(self):
    pass

Then you go and write what it should actually do (call the action) with "# do" comment before that.

def test_should_sort_by_name(self):
    # do
    bl_shopping_list.sort_from_params(query, params)

Then, you go and fill all the necessary mocks and args for your action to run:

def test_should_sort_by_name(self):
    query = Mock()
    params = {'sort': '-name'}

    # do
    bl_shopping_list.sort_from_params(query, params)

At the end, you write your assert.

def test_should_sort_by_name(self):
    query = Mock()
    params = {'sort': '-name'}

    # do
    bl_shopping_list.sort_from_params(query, params)

    query.order_by.assert_called_with('-name', '-updated_at')

And at last, if necessarily, you will add all the @patch before method. That's an idea of building a test without a lot of thinking about what to start from, and moving step-by-step from smaller pieces to whole-picture of test.

Let's move on to :doc:`change-the-way`.