Fixtures and Setup

Not only do we want to look at production code for refactoring opportunities, we want to look at the tests, too. Both our tests require the same line of code to create a Soundex instance. We’re not happy with even such seemingly trivial duplication. It adds up quickly and often turns into more complex duplication. It also clutters the tests, detracting from what’s important for a reader to understand.

It’s common for related tests to need common code. Google Mock lets us define a fixture class in which we can declare functions and member data for a related set of tests. (Technically, all Google Mock tests use a fixture that it generates behind the scenes.)

c2/10/SoundexTest.cpp
*
class​ SoundexEncoding: ​public​ testing::Test {
*
public​:
*
Soundex soundex;
*
};
 
*
TEST_F(SoundexEncoding, RetainsSoleLetterOfOneLetterWord) {
 
auto​ encoded = soundex.encode(​"A"​);
 
 
ASSERT_THAT(encoded, Eq(​"A000"​));
 
}
 
*
TEST_F(SoundexEncoding, PadsWithZerosToEnsureThreeDigits) {
 
auto​ encoded = soundex.encode(​"I"​);
 
 
ASSERT_THAT(encoded, Eq(​"I000"​));
 
}

We create the SoundexEncoding fixture (which must derive from ::testing::Test) so that creating a Soundex instance gets done in one place. Within the fixture, we declare a soundex member variable and make it public so that the tests have visibility to it. (If you’re concerned about exposing soundex, remember that our fixture class lives in a .cpp file. We’d prefer to avoid every piece of unnecessary clutter in our tests.)

Google Mock instantiates the fixture class once per test. Before Google Mock executes RetainsSoleLetterOfOneLetterWord, it creates a SoundexEncoding instance, and before it executes PadsWithZerosToEnsureThreeDigits, it creates a separate SoundexEncoding instance. To code a custom fixture, we change the TEST macro invocation to TEST_F, with the F standing for “Fixture.” If we forget to use TEST_F, any test code attempting to use fixture member errors will fail compilation.

We delete the local declarations of soundex, since the member is available to each test. We make this change incrementally. After coding the fixture and changing the macro, we delete the local declaration of soundex from the first test. We run all the tests to verify the change, remove the declaration from the second test, and run all the tests again.

Getting rid of the duplicate Soundex declaration does at least a couple things.

  • It increases the abstraction level of our tests. We now see only two lines in each test, which allows us to focus on what’s relevant. We don’t see the irrelevant detail of how the Soundex object gets constructed (see Test Abstraction for more information on why this is important).

  • It can reduce future maintenance efforts. Imagine we have to change how we construct Soundex objects (perhaps we need to be able to specify a language as an argument). Moving Soundex construction into the fixture means we would need to make our change in only one place, as opposed to making the change across several tests.

With only two lines each, our tests are a little easier to read. What else might we do? We can reduce each test to a single line without losing any readability. I’m also not a fan of explicit using directives, so we clean that up too.

c2/11/SoundexTest.cpp
 
#include "gmock/gmock.h"
*
#include "Soundex.h"
 
*
using​ ​namespace​ testing;
 
*
class​ SoundexEncoding: ​public​ Test {
 
public​:
 
Soundex soundex;
 
};
 
 
TEST_F(SoundexEncoding, RetainsSoleLetterOfOneLetterWord) {
*
ASSERT_THAT(soundex.encode(​"A"​), Eq(​"A000"​));
 
}
 
 
TEST_F(SoundexEncoding, PadsWithZerosToEnsureThreeDigits) {
*
ASSERT_THAT(soundex.encode(​"I"​), Eq(​"I000"​));
 
}

You’ll note that the test now refers to Soundex.h. Having the tests and code in a single file was helpful for a short while. Now, the bouncing up and down in a single file is getting old. We split into the test and the header (we’ll decide whether we should create an .impl file when we’re done). Here’s the header:

c2/11/Soundex.h
*
#ifndef Soundex_h
*
#define Soundex_h
 
#include <string>
 
 
class​ Soundex
 
{
 
public​:
 
std::​string​ encode(​const​ std::​string​& word) ​const​ {
 
return​ zeroPad(word);
 
}
 
 
private​:
 
std::​string​ zeroPad(​const​ std::​string​& word) ​const​ {
 
return​ word + ​"000"​;
 
}
 
};
 
*
#endif
..................Content has been hidden....................

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