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!