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)
      0
      >>> add(0, 1)
      1
      >>> add(1, 0)
      1
      >>> add(1, 1)
      2
      """
      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'
    end
    it "returns 'Prrr' after eating" do
      cat = Cat.new
      cat.eat
      cat.talk.should == 'Prrr'
    end
  end
end

When you run the tests this is the output:

Cat
  #talk
    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

Examples:
  | 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,

David.

Comments

Popular posts from this blog

Restart the Windows File Sharing Service to fix weird problems

WPF, ImageSource and Embedded Resources

SharpDevelop dark color scheme