Incrementalism

It’s question-and-answer (Q&A) time!

Q.:

Do you really code like this, hard-coding things that you know you’ll replace?

A.:

I always get this question. Yes.

Q.:

It seems stupid!

A.:

That’s not a question, but yes, it’s an OK first reaction to think this is stupid. It felt stupid to me at first, too. I got over it.

Q.:

Are we going to keep working like this? How will we get anything done if we hard-code everything?

A.:

That’s two questions, but I’m happy to answer them both! Yes, we will keep working incrementally. This technique allows us to get a first passing test in place quickly. No worries, the hard-coded value will last only minutes at most. We know we’re not done with what we need to build, so we’ll have to write more tests to describe additional behavior. In this example, we know we must support the rest of the rules. As we write additional tests, we’ll have to replace the hard-coding with interesting logic in order to get the additional tests to pass.

Incrementalism is at the heart of what makes TDD successful. An incremental approach will seem quite unnatural and slow at first. However, taking small steps will increase your speed over time, partly because you will avoid errors that arise from taking large, complicated steps. Hang in there!

Astute readers will note that we’ve already coded something that does not completely meet the specification (spec) for Soundex. The last part of rule #4 says that we must “fill in zeros until there are three numbers.” Oh, the joy of specs! We must read them comprehensively and carefully to fully understand how all their parts interact. (Better that we had a customer to interact with, someone who could clarify what was intended.) Right now it seems like rule #4 contradicts what we’ve already coded.

Imagine that the rules are being fed to us one by one. “Get the first part of rule #1 working, and then I’ll give you a new rule.” TDD aligns with this latter approach—each portion of a spec is an incremental addition to the system. An incremental approach allows us to build the system piecemeal, in any order, with continually verified, forward progress. There is a trade-off: we might spend additional time incorporating a new increment than if we had done a bit more planning. We’ll return to this concern throughout the book. For now, let’s see what happens when we avoid worrying about it.

We have two jobs: write a new test that describes the behavior, and change our existing test to ensure it meets the spec. Here’s our new test:

c2/9/SoundexTest.cpp
 
TEST(SoundexEncoding, PadsWithZerosToEnsureThreeDigits) {
 
Soundex soundex;
 
 
auto​ encoded = soundex.encode(​"I"​);
 
 
ASSERT_THAT(encoded, Eq(​"I000"​));
 
}

(One reviewer asks, “Why didn’t we read the Soundex rules more carefully and write this first?” Good question. Indeed, we weren’t careful. A strength of TDD is its ability to let you move forward in the face of incomplete information and in its ability to let you correct earlier choices as new information arises.)

Each test we add is independent. We don’t use the outcome of one test as a precondition for running another. Each test must set up its own context. Our new test creates its own Soundex instance.

A failing test run shows that encode returns the string "I" instead of "I000". Getting it to pass is straightforward.

c2/9/SoundexTest.cpp
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
*
return​ word + ​"000"​;
 
}

Hard-coding an answer may again ruffle feathers, but it will help us keep on track. Per our tests so far, the Soundex class requires no additional behavior. Also, by building the smallest possible increment, we’re forced to write additional tests in order to add more behavior to the system.

Our new test passes, but the first test we wrote now fails. The behavior it describes, by example, does not match the specification we derived from Wikipedia.

When done test-driving, you’ll know that the tests correctly describe how your system works, as long as they pass. They provide examples that can read easier than specs, if crafted well. We’ll continue to focus on making the tests readable in our exercises (and I might even casually refer to them as specs).

c2/9/SoundexTest.cpp
 
TEST(SoundexEncoding, RetainsSoleLetterOfOneLetterWord) {
 
Soundex soundex;
 
 
auto​ encoded = soundex.encode(​"A"​);
 
*
ASSERT_THAT(encoded, Eq(​"A000"​));
 
}

That wasn’t too tough!

We now have two tests that perform the same steps, though the data differs slightly. That’s OK; each test now discretely documents one piece of behavior. We not only want to make sure the system works as expected, we want everyone to understand its complete set of intended behaviors.

It’s time for refactoring. The statement in encode isn’t as clear about what’s going on as it could be. We decide to extract it to its own method with an intention-revealing name.

c2/10/SoundexTest.cpp
 
public​:
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
*
return​ zeroPad(word);
 
}
 
 
private​:
*
std::​string​ zeroPad(​const​ std::​string​& word) ​const​ {
*
return​ word + ​"000"​;
*
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.189.251