Elentok's Blog

About me

How I learned TDD

I first heard about unit tests at an open source developer conference in 2006 and since then I've been trying to understand this concept.

The first tests I wrote were with PyUnit, I don't remember what the project was, I just remember that it was horribly implemented, I didn't have any notion of the SOLID principles, so it was very difficult to write the tests and eventually I gave it up.

A couple of years later I started to develop a large enterprise application in .NET which forced me to improve my software architecture skills, I started reading a lot of books on the subject:

As I was reading the books I began to realize how little I understand about software craftsmanship and how much more there is to learn.

So I started writing tests again (still after the code itself), I could see the value of the tests, since it helped the stability of the application I was working on (to this day there aren't any new bugs).

But the tests still didn't feel right, they didn't cover all of the scenarios, they were annoying to write, I kept having to change the original after adding the tests.

Later on I wrote a todo.txt desktop editor in Python and QT, I looked for a testing framework and found doctest which allows you to write the tests inside the code (or in simple text files), like this:

def add(x, y):
    >>> add(0, 0)
    >>> add(0, 1)
    >>> add(1, 0)
    >>> add(1, 1)
    return x+y

As you can see the docstring includes 4 tests (0+0, 0+1, 1+0 and 1+1).

This was my first attempt at TDD, it wasn't perfect, some tests were written before and some were written after the implementation, but I could feel I was starting to get it (you can see the git code in bitbucket.

A few years ago I started getting into Ruby on Rails, and the smartest thing I did was subscribe to Gary Bernhardt's Destroy All Software screencasts, most of what I know now about TDD I learned from these screencasts (so thank you Gary!).

I discovered an amazing testing framework called RSpec where you write your test in a much more readable format:

describe Cat do
  describe "#talk" do
    it "returns 'Meow'" do
      cat = Cat.new
      cat.talk.should == 'Meow'
    it "returns 'Prrr' after eating" do
      cat = Cat.new
      cat.talk.should == 'Prrr'

When you run the tests this is the output:

    returns 'Meow'
    returns 'Prrr' after eating

The hierarchy and the plain text for examples and contexts make the output perfect as documentation for the code.

I learned that there are several kinds of tests:

  • Unit tests: test methods or classes in isolation, all external dependencies are stubbed (if a test fails you will usually find the error in a very narrow area of code).
  • Integration tests: test the integration between several methods or classes, some of the external dependencies can be stubbed (if a test fails its harder to find the error).
  • Acceptance tests: test the system as a whole, without any stubs. For example, open the web application in a browser and check that certain elements exist, or execute a command line application and check that the output matches what was expected.

I developed various Rails applications, and with each application my technique and my understanding of TDD improved, I could feel I was making progress. Back then I only wrote unit and integration tests.

I also started using a javascript testing framework called Jasmine which is basically RSpec for javascript.

At first, I was progressing slowly, working with TDD was much slower than non-TDD because I kept getting stuck on tests I wasn't sure how to write. But after a few months watching lots of screencasts, reading tons of blog posts on TDD, and spending a lot of time trying to improve my code and my tests I got to a stage where I'm working faster with TDD than without.

Whenever I write code without writing the test first, it just feels wrong and sloppy, and I can't make excuses anymore, since it doesn't take more time.

About a month ago I read The RSpec Book and it filled all of the holes, finally I had a full understanding of an entire BDD process that felt right to me:

  • Write the user stories
  • Select the user stories for the current iteration
  • Write a cucumber feature for each story (with all of the required scenarios, I find it best to write the scenarios with a QA person or a product owner).
  • Now start making the scenarios pass using ordinary TDD (write the actual code with unit and integration tests first).
  • When all of the scenarios of a feature are passing, it basically means the feature is complete (now you need to do some exploratory testing to see if it really works).

As I understand it there are two ways to use Cucumber:

  • QA tool - writing super-specific tests (using the "evil" websteps, I click this, I enter that, I see this...), this way your feature files get huge and you can no longer use it for documentation because it's too long and no one will read it.
  • BDD tool - write the acceptance tests from the stories in an abstract way, so the feature files are easier to scan and understand.

I know a lot of people don't like using Cucumber, and at first I thought so too, but after reading the RSpec book and trying it out I think that as long as you're using it as a BDD tool it can actually be more comfortable then rspec (for acceptance tests), I like the the Scenario Examples feature allows to you to tests multiple inputs in a pretty clean way:

Scenario Outline: Add two numbers
  Given I have entered <input_1> into the calculator
  And I have entered <input_2> into the calculator
  When I press <button>
  Then the result should be <output> on the screen

  | input_1 | input_2 | button | output |
  | 20      | 30      | add    | 50     |
  | 2       | 5       | add    | 7      |
  | 0       | 40      | add    | 40     |

The cucumber tests filled the gap between the user stories and the integration/unit tests for me and they help me stay much focused and a have a better indication of "done".

To sum things up, the three things I like most about this process:

  • My code is a lot more stable.
  • Helps me focus.
  • I'm discovering my code instead of guessing it into existence.

Hope you enjoyed this post, thanks for reading,


Next:Writing hebrew text using Vim