Design Will Change

Your first reaction to test doubles may be that using them will change your approach to design. You might find that prospect unsettling. Don’t worry, it’s a natural response.

Cohesion and Coupling

When faced with a troublesome dependency (such as a slow or volatile collaborator), the best option is to isolate it to a separate class. Granted, making an HTTP request isn’t very involved. Putting the logic in a small, separate Http class might not seem worth the effort, but you’ll have more potential for reuse and more design flexibility (the ability to replace it with a polymorphic substitute, for example). You’ll also have a few more options when it comes to creating a test double.

The alternative is to create more procedural, less cohesive code. Take a look at a more typical solution for the PlaceDescriptionService, created in the world of test-after:

c5/17/PlaceDescriptionService.cpp
 
string​ PlaceDescriptionService::summaryDescription(
 
const​ ​string​& latitude, ​const​ ​string​& longitude) ​const​ {
 
// retrieve JSON response via API
 
response_ = ​""​;
 
auto​ url = createGetRequestUrl(latitude, longitude);
 
curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
 
curl_easy_perform(curl);
 
curl_easy_cleanup(curl);
 
 
// parse json response
 
Value location;
 
Reader reader;
 
reader.parse(response_, location);
 
auto​ jsonAddress = location.get(​"address"​, Value::null);
 
 
// populate address from json
 
Address address;
 
address.road = jsonAddress.get(​"road"​, ​""​).asString();
 
address.city = jsonAddress.get(​"hamlet"​, ​""​).asString();
 
address.state = jsonAddress.get(​"state"​, ​""​).asString();
 
address.country = jsonAddress.get(​"country"​, ​""​).asString();
 
 
return​ address.road + ​", "​ + address.city + ​", "​ +
 
address.state + ​", "​ + address.country;
 
}

The implementation is but twenty lines that read fairly well, particularly with the guiding comments. It’s typical of most test-after code. While it could be broken into several smaller functions, like we did earlier, developers often don’t bother. Test-after developers aren’t as habituated to regular refactoring, and they don’t usually have the fast tests needed to make it quick and safe. So what? Is there anything wrong with code that looks like this? We could refactor it if and when we needed.

From a design stance, the twenty lines violate the SRP—many reasons exist for summaryDescription to change. The function is tightly coupled to cURL. Further, the twenty lines represent the code smell known as Long Method, making for code that requires too much time to fully understand.

Longer functions like this promote unnecessary duplication. Other services are likely to need some of the same cURL logic, for example. Developers will often re-code the three lines related to cURL rather than try to reuse them. Reuse begins with isolation of reusable constructs that other programmers can readily identify. As long as the potentially reusable chunks of code lay buried in a Long Method, reuse won’t happen.

Build a system this way, and you’ll create double the lines of code.

You can still test the twenty lines. You can use link substitution to support writing a fast unit test (see Creating a Test Double for rlog). Or you can write an integration test that generates a live call to the REST service. But both types of test will be larger, with more setup and verification in a single test (though, overall, the amount of initial test coding effort isn’t much different). The integration test will be slow and brittle.

The bulk of code in the world looks even worse than these twenty lines. Your code will look better, because you will seek cohesive, decoupled designs as you practice TDD. You’ll start to realize the benefit of a more flexible design. You’ll quickly discover how these better designs align with tests that are much smaller and easier to write, read, and maintain.

Shifting Private Dependencies

If you weren’t concerned about testing, the HTTP call in the PlaceDescriptionService could remain a private dependency, meaning that clients of PlaceDescriptionService would be oblivious to the existence of the HTTP call. When you use setter or constructor injection, however, clients take on the responsibility of creating Http objects and passing them in. You shift the dependency of PlaceDescriptionService to the client.

Developers can be concerned about the ramifications of this choice.

Q.:

Doesn’t setter or constructor injection violate the notion of information hiding?

A.:

From the stance of the client, yes. You can use an alternate form of dependency injection (see Getting Test Doubles in Place). The information you’re exposing is unlikely to cause future grief if someone takes advantage of it.

You also have the option of providing a default instance. We can configure PlaceDescriptionService to contain a CurlHttp instance that gets replaced when the test provides an HttpStub. The production client need not change.

Q.:

But what if a nefarious developer chooses to pass in a destructive Http instance?

A.:

Choose an alternate injection form if clients are outside your team. If you’re worried about developers within the team deliberately taking advantage of the injection point to do evil things, you have bigger problems.

Q.:

TDD is growing on me, but I’m concerned about changing how I design solely for purposes of testing. My teammates probably feel the same way.

A.:

Knowing that software works as expected is a great reason to change the way you design code. Have this conversation with your teammates: “I’m more concerned about whether the code works. Allowing this small concession means we can more easily test our code, and getting more tests in place can help us shape the design more easily and trust the code more. Can we rethink our standards?”

..................Content has been hidden....................

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