Engineering in Focus

the Fandor engineering blog

TDD, not DDT: Doing TDD in the right direction

with one comment

Here at Fandor we try to do test-driven development (TDD). I like to say that either we test first or, if we can’t figure out how, we test second. So when you interview we ask you to test-drive a little code. Watching a couple of dozen people (we change the problem every round of hiring to keep plagiarism down) solve the same little problem is pretty interesting: not only do you learn a lot about each individual and about the problem, but you also get a good idea of how the methods you’re using work best.

In our current round of hiring I’ve noticed an antipattern in how many of our applicants do TDD, which is that they test from the bottom up instead of from the top down. This is old news if you’ve read up on BDD, which says test outside in rather than inside out, but the message seems not to have gotten to everyone so let’s review.

By “bottom up” or “inside out” I mean testing the details first, perhaps after a hasty but not conclusive (i.e. not resulting in any passing tests) consideration of the problem and what details someone thinks they need. In the programming part of our interviews it might go something like this:

(The task: write a program that allows two humans to play chess on a virtual board. Don’t worry about the UI, just write methods that allow an imaginary UI to ask whether a proposed move is valid and make a move.)

“Let’s see … I’ll need a list of pieces that can move. Well, I’ll need a Game first, so let’s make one …”

describe Game do
  describe '#initialize' do
    it "constructs a Game" do
      Game.new.should be_a Game
    end
  end
end
 

(makes the class, requires it, etc.)

“OK, now I can write the method. Let’s write a test to be sure that it’s there …”

describe '#pieces_that_can_move' do
  it "is defined" do
    Game.new.should respond_to(:pieces_that_can_move)
  end
end
 

(defines an empty method)

“Now let’s actually test the logic …”

describe '#pieces_that_can_move' do
  it "is defined" do
    Game.new.pieces_that_can_move(:white).should =~
      [[0, 1], [1, 1], [2, 1], [3, 1], [4, 1], [5, 1], [6, 1], [7, 1], [1, 0], [6, 0]]
  end
end
 

(implement implement implement)

“OK, now let’s use that to check the player’s move.”

(maybe we have to sit through the respond_to business again, but eventually we get around to writing)

describe '#move_is_valid?' do
  it "returns true if the given piece can move to the given location" do
    Game.new.move_is_valid?(0, 1, 0, 2).should be_true # rook's pawn moves forward one square
  end
end
 

“Oh, wait … pieces_that_can_move doesn’t tell me if the move is valid, too. I guess I need another method.”

Wow, we just wrote a lot of code that doesn’t do what we want! That example for Game#initialize will probably pass no matter what we write, but admit it, it’s worthless — if we don’t have any examples that do anything interesting with a Game, it doesn’t matter if we can construct it or not. TDD backwards is DDT — an insidious poison that will weaken your eggshells and — anyway, it’s bad for you.

It’s probably obvious what I’m going to argue is the right way to proceed: test-drive your code top down. Write an example that tests something that we actually want the program to do for us. For this little exercise we were asked to write methods that do something, so let’s just start with a test of one of those methods. In fact, the last test we wrote, of move_is_valid?, is the first test we should have written.

All right, we’ll delete all of the code other than that test and start over.

“Gee, there’s a lot of code to write to decide where exactly a pawn can move, isn’t there? That’s why I wanted to write some low-level methods that I could put together.”

But, as we just discovered, our big brains don’t always think of the low-level methods that we’ll actually need. Instead, just take it a test at a time and refactor. All it takes to make that first test pass is

class Game
  def move_is_valid? from_x, from_y, to_x, to_y
    true
  end
end
 

That won’t get you very far at the chess club, but let’s just write another test:

describe '#move_is_valid?' do
  it "returns false if a pawn is to move sideways" do
    Game.new.move_is_valid?(0, 1, 1, 1).should be_false
  end
end
 

OK, now we’re forcing ourselves to implement that method a little more meaningfully. Let’s try

class Game
  def move_is_valid? from_x, from_y, to_x, to_y
    from_x == to_x
  end
end
 

That wasn’t so bad. Let’s do it again.

describe '#move_is_valid?' do
  it "returns false if a pawn is to move three spaces forward" do
    Game.new.move_is_valid?(0, 1, 0, 3).should be_false
  end
end

class Game
  def move_is_valid? from_x, from_y, to_x, to_y
    from_x == to_x && to_y - from_y == 1
  end
end
 

And so on. After another few tests we’ll be forced to actually represent the pieces on the board, distinguish black from white, distinguish pawns from rooks from etc., and implement all the other rules of chess. We’ll find that some of the code we’ve written doesn’t handle the new tests we think of, but we can refactor to make room for new requirements and our existing tests will catch breakage. We’ll never need to guess what code we need, only write what we need when we need it, and we’ll only write examples that actually capture requirements.

There are of course a lot more nuances to TDD than anyone wants to see crammed in to one blog post. Kent Beck’s TDD by Example is the first and best presentation of a long example with all the trimmings, and The RSpec Book is where to turn when you want “outside in” to mean starting outside the entire application, using Cucumber to express actual user scenarios and then filling in the details with rspec.

But the basic principle is this simple: Take it from the top, not from the bottom. Implement requirements, not guesses.

About these ads

Occasionally, some of your visitors may see an advertisement here.

Tell me more | Dismiss this message

Written by fandave Edit

January 29, 2014 at 18:46

Posted in Agile development

One Response

Subscribe to comments with RSS.

  1. […] TDD, not DDT: Doing TDD in the right direction […]


Leave a Reply

Skip to toolbar Log Out