Nikolay Sturm's Blog

Musings about Development and Operations

Insights Into TDD

| Comments

About two years ago I began my journey into professional software development. With it came the urge of mastering TDD, a practice I had long observed as a bystander.

While I found and ingested lots of material about TDD, turning the knowledge into praxis was astonishingly hard for me. It was only a couple of weeks ago that it finally clicked and I felt the pieces comming together.

I won’t describe any new techniques in this post. Everything presented herein, I read somewhere. However, I hope this collection of techniques will help others getting TDD faster.

For the remainder of this post, I’ll talk about a small application that scrapes a website for the component companies of a stock index. I developed it using BDD’s approach of outside-in development.

Focus on the Problem Domain

Whenever I wanted to use TDD for a new feature or a completely new side project, I was faced with the problem of having to formulate my first acceptance test. I could never figure out how to get started. How could I formulate a test for code that I had no idea of?

When I began writing the stock index scraper, I started with an acceptance test for the scraper class and was blocked as usual. I had no idea about what that code should actually do and with what other classes it would interact.

At that point I reminded myself of focus on the problem domain, not the solution domain. My job was not to write a website scraper, but instead I wanted a program to update the list of stock index components.

With that in mind, I could formulate the first acceptance test.

feature 'Updates the list of tracked equities' do
  scenario 'with all DAX stocks' do
  end
end

The next question became what my application’s entry point should be.

Use Case Classes

A technique I had picked up from Corey Haines and others was use case classes. Usually you name classes after domain nouns, but sometimes it makes more sense to go with a verby name, describing a use case. Here, the acceptance test already provides a sensible use case, so my application’s entry point ended up being UpdatesListOfTrackedEquities.execute().

This class/method should go through all equities in the DAX and save a record to the database. To keep it simple, I just checked the amount of records in the database afterwards and the final acceptance test became:

feature 'Updates the list of tracked equities' do
  scenario 'with all DAX stocks' do
    UpdatesListOfTrackedEquities.execute

    expect(Equity.count).to eq 30
  end
end

Canned Values for Acceptance Tests

Another technique I picked up from Corey (and later J. B. Rainsberger) was getting the test green quickly by using canned values. This is normal practice when doing code katas, but I hadn’t thought about doing it with an acceptance test before.

class Equity
  def self.count
    30
  end
end

Later, when TDDing lower level behaviour, I would eventually get back to Equity.count() and provide a proper implementation.

I find this technique quite motivating, as it provides a green test suite early on. Keeping the acceptance test broken would often distract me from the task at hand.

Discover Interfaces

Next up I had to implement UpdatesListOfTrackedEquities.execute(). Again focussing on the problem domain, I came up with a simple unit test.

There are different stock exchanges in germany and the online XETRA platform was the one I wanted to work with. I wanted to track stocks from different indices, so I needed a class for each index on XETRA. This class would represent the stock index, so it made sense to have a method .constituents() that would return the list of companies making up that index.

describe UpdatesListOfTrackedEquities do
  describe '.execute' do
    let(:company1) { double('Company') }
    let(:company2) { double('Company') }

    it 'tracks all DAX companies on XETRA' do
      XETRA::DAX.stub(:constituents).and_return([company1, company2])

      Equity.should_receive(:track).with(company1)
      Equity.should_receive(:track).with(company2)

      UpdatesListOfTrackedEquities.execute
    end
  end
end

At this moment I realized that interface discovery was all about thinking in the problem domain.

Only when implementing XETRA::DAX.constituents() had I finally to create the website scraper. The class that would actually do most of the hard work.

Class Interfaces vs. Library Interfaces

When implementing the website scraper, I had to use external libraries like httpclient or nokogiri. At this point my TDD practice somehow broke down and I didn’t create more intermediate objects like interface classes, but instead I just used those libraries directly from the scraper class. At first I felt bad about it, but then I came to the conclusion that this might actually be sensible.

One advice I often read was to wait for several similar use cases before abstracting an interface. I was always mildy confused as to how this related to interface discovery. At this point in my sample application, I concluded that interface abstraction is the way to go when using an existing class, often in the form of an external library. Interface discovery, on the other hand, was the way to go when creating new domain model classes.

This is in line with one reason for creating library interfaces, which is to encapsulate library usage in a single class. If my website scraper is the only class using, e.g. httpclient, its usage is limited to one class. Therefor, I don’t gain much by putting an interface in front of it. Once I have two or three users of httpclient, I see how my application actually uses the library and I can put an interface in front of it.

Conclusion

It took me quite some time to wrap my head around TDD. Thinking in the problem domain had the most impact on me. Once I realised that, the other pieces fell into place.

What do you think? Was this all out there and I just didn’t see it? Did I misunderstand anything? Share your ideas in the comments below!

Comments