Organization

How do you organize your tests, from both a file standpoint and a logical one? In this section, you’ll learn about how to group your tests using fixtures, as well as how to take advantage of their setup and teardown hooks. You’ll learn how to approach organization within your tests as well, using the concept of Given-When-Then (also known as Arrange-Act-Assert).

File Organization

You test-drive related behavior by defining tests in a single test file. For example, to test-drive a RetweetCollection class (implemented in RetweetCollection.cpp/h), start with RetweetCollectionTest.cpp. Don’t create a header file for your test functions—it demands extra effort for no value.

You may end up with multiple test files that verify related behavior. You may also have one test file cover behavior located in a few areas. Don’t limit yourself to one test file per class. Setup and Teardown, provides one reason why you’d want multiple test files per class.

Name the file based on the tests it contains. Summarize the related behavior and encode it in the test name. Decide on a naming scheme, something like BehaviorDescriptionTest.cpp, BehaviorDescriptionTests.cpp, or TestBehaviorDescription.cpp. It doesn’t matter which as long as your codebase consistently adheres to the standard. A consistent naming convention will help programmers readily locate the tests they seek.

Fixtures

Most unit testing tools let you logically group related tests. In Google Mock, you group related tests using what Google calls the test case name. The following test declaration has a test case name of ARetweetCollection. IncrementsSizeWhenTweetAdded is the name of a test within the test case.

 
TEST(ARetweetCollection, IncrementsSizeWhenTweetAdded)

Related tests need to run in the same environment. You’ll have many tests that require common initialization or helper functions. Many test tools let you define a fixture—a class that provides support for cross-test reuse.

In Google Mock, you define a fixture as a class derived from ::testing::Test. You typically define the fixture at the beginning of your test file.

 
using​ ​namespace​ ::testing;
 
 
class​ ARetweetCollection: ​public​ Test {
 
};

The following two tests exhibit duplication; they both create an instance of RetweetCollection:

 
TEST(ARetweetCollection, IsEmptyWhenCreated) {
*
RetweetCollection collection;
 
 
ASSERT_THAT(collection.isEmpty(), Eq(true));
 
}
 
 
TEST(ARetweetCollection, IsNoLongerEmptyAfterTweetAdded) {
*
RetweetCollection collection;
 
collection.add(Tweet());
 
 
ASSERT_THAT(collection.isEmpty(), Eq(false));
 
}

You can eliminate the local definitions of RetweetCollection by defining RetweetCollection once in the fixture.

 
class​ ARetweetCollection: ​public​ Test {
 
public​:
*
RetweetCollection collection;
 
};

To give a test access to members defined in the fixture class, you must change the TEST macro definition to TEST_F (the trailing F stands for fixture). Here are the cleaned-up tests:

 
TEST_F(ARetweetCollection, IsEmptyWhenCreated) {
 
ASSERT_THAT(collection.isEmpty(), Eq(true));
 
}
 
 
TEST_F(ARetweetCollection, IsNoLongerEmptyAfterTweetAdded) {
 
collection.add(Tweet());
 
 
ASSERT_THAT(collection.isEmpty(), Eq(false));
 
}

The test case name must match the fixture name! If not or if you forget to append _F to the macro name, you’ll get a compile error where the tests reference elements from the fixture (collection in this example).

Creation of a collection variable is a detail that test readers don’t usually need to see in order to understand what’s going on. Moving the instantiation to a fixture removes that distraction from each of the two tests in this example. If you remove an element from a test in this manner, reread the test. If its intent remains clear, great. If not, undo the change, or consider renaming the variable to make the intent obvious.

You can and should also move all common functions into the fixture, particularly if you are not enclosing your tests within a namespace.

Setup and Teardown

If all of the tests in a test case require one or more statements of common initialization, you can move that code into a setup function that you define in the fixture. In Google Mock, you must name this member function SetUp (it overrides a virtual function in the base class ::testing::Test).

For each test belonging to the fixture, Google Mock creates a new, separate instance of the fixture. This isolation helps minimize problems created when the statements executed in one test impact another test. The implication is that each test must create its own context from scratch and not depend upon the context created by any other tests. After creating the fixture instance, Google Mock executes the code in SetUp and then executes the test itself.

Both of the following tests add a Tweet object to the collection in order to create an initial context:

c3/14/RetweetCollectionTest.cpp
 
TEST_F(ARetweetCollection, IsNoLongerEmptyAfterTweetAdded) {
 
collection.add(Tweet());
 
ASSERT_FALSE(collection.isEmpty());
 
}
 
 
TEST_F(ARetweetCollection, HasSizeOfOneAfterTweetAdded) {
 
collection.add(Tweet());
 
ASSERT_THAT(collection.size(), Eq(1u));
 
}

We define a new fixture for RetweetCollection tests that represents a collection populated with a single tweet. We choose a fixture name that describes that context: ARetweetCollectionWithOneTweet.

c3/15/RetweetCollectionTest.cpp
 
class​ ARetweetCollectionWithOneTweet: ​public​ Test {
 
public​:
 
RetweetCollection collection;
 
void​ SetUp() override {
 
collection.add(Tweet());
 
}
 
};

We take advantage of the common initialization, creating tests that are ever-so-simpler to read.

c3/15/RetweetCollectionTest.cpp
 
TEST_F(ARetweetCollectionWithOneTweet, IsNoLongerEmpty) {
 
ASSERT_FALSE(collection.isEmpty());
 
}
 
 
TEST_F(ARetweetCollectionWithOneTweet, HasSizeOfOne) {
 
ASSERT_THAT(collection.size(), Eq(1u));
 
}

When removing duplication, be careful not to remove information critical to understanding the test. Does the fixture name clearly describe the context? ARetweetCollectionWithOneTweet seems self-explanatory, but then again it’s easy to convince yourself. Ask another programmer. If they’re compelled to read the fixture’s setup code, find a way to make the test more explicit.

The first test previously had the test case.test name combination ARetweetCollection.IsNoLongerEmptyAfterTweetAdded (Google Mock refers to this combination as its full name). Now the full name is ARetweetCollectionWithOneTweet.IsNoLongerEmpty. We can avoid having to encode redundant descriptions of our context into each test name by using fixtures with names that describe the context.

We have one more test we can clean up.

c3/15/RetweetCollectionTest.cpp
 
TEST_F(ARetweetCollection, IgnoresDuplicateTweetAdded) {
 
Tweet tweet(​"msg"​, ​"@user"​);
 
Tweet duplicate(tweet);
 
collection.add(tweet);
 
collection.add(duplicate);
 
 
ASSERT_THAT(collection.size(), Eq(1u));
 
}

The first Tweet object added in the test differs from the Tweet objects created in the other tests; it passes values for both of the Tweet’s constructor arguments, whereas the other Tweet objects are initialized using default arguments. But we don’t really care what Tweet object ends up in the collection in those tests, just that the collection contains a tweet, any tweet. We choose to use the more interesting tweet for all tests.

c3/16/RetweetCollectionTest.cpp
 
class​ ARetweetCollectionWithOneTweet: ​public​ Test {
 
public​:
 
RetweetCollection collection;
 
void​ SetUp() override {
*
collection.add(Tweet(​"msg"​, ​"@user"​));
 
}
 
};

But we need to reference that tweet from IgnoresDuplicateTweetAdded in order to create a second, duplicate Tweet object. We’ll want to introduce a member variable to the ARetweetCollectionWithOneTweet fixture. That means we’ll have to declare it as a pointer type that we initialize in setup. We can use regular ol’ C++ pointers that we delete in a teardown function.

c3/17/RetweetCollectionTest.cpp
 
class​ ARetweetCollectionWithOneTweet: ​public​ Test {
 
public​:
 
RetweetCollection collection;
 
Tweet* tweet;
 
void​ SetUp() override {
 
tweet = ​new​ Tweet(​"msg"​, ​"@user"​);
 
collection.add(*tweet);
 
}
 
 
void​ TearDown() override {
 
delete​ tweet;
 
tweet = nullptr;
 
}
 
};

The teardown function is essentially the opposite of the setup function. It executes after each test, even if the test threw an exception. You use the teardown function for cleanup purposes—to release memory (as in this example), to relinquish expensive resources (for example, database connections), or to clean up other bits of state, such as data stored in static variables.

Using pointers requires a small change to our test, since the tweet variable now requires pointer semantics. The exciting payoff is that we are able to reduce the number of lines in the test from five to three that represent the core of what we’re demonstrating in the test.

c3/17/RetweetCollectionTest.cpp
 
TEST_F(ARetweetCollectionWithOneTweet, IgnoresDuplicateTweetAdded) {
*
Tweet duplicate(*tweet);
 
collection.add(duplicate);
 
 
ASSERT_THAT(collection.size(), Eq(1u));
 
}

Here’s a version of the fixture that uses smart pointers:

c3/18/RetweetCollectionTest.cpp
 
class​ ARetweetCollectionWithOneTweet: ​public​ Test {
 
public​:
 
RetweetCollection collection;
*
shared_ptr<Tweet> tweet;
 
 
void​ SetUp() override {
*
tweet = shared_ptr<Tweet>(​new​ Tweet(​"msg"​, ​"@user"​));
 
collection.add(*tweet);
 
}
 
};

Setup initialization code should apply to all associated tests. Setup context that only a fraction of tests use creates unnecessary confusion. If some tests require a tweet and some tests don’t, create a second fixture and apportion the tests appropriately.

Don’t hesitate to create additional fixtures. But each time you create a new fixture, determine whether that need represents a design deficiency in the production code. The need for two distinct fixtures might indicate that the class you’re testing violates the SRP, and you might want to split it into two.

Arrange-Act-Assert/Given-When-Then

Tests have a common flow. A test first sets up proper conditions, then executes code representing the behavior that you want to verify, and finally verifies that the expected behavior occurred as expected. (Some tests may also need to clean up after themselves. For example, a test may need to close a database connection opened by earlier-executing test code.)

Your tests should declare intent with as much immediacy as possible. That means your tests shouldn’t require readers to slowly slog through each line of the test, trying to discern what’s going on. A reader should readily understand the essential elements of a test’s setup (arrange), what behavior it executes (act), and how that behavior gets verified (assert).

The mnemonic Arrange, Act, Assert (AAA, usually spoken as triple-A), devised by Bill Wake,[10] reminds you to visually organize your tests for rapid recognition. Looking at this next test, can you quickly discern which lines of the test relate to setting up a context, which line represents execution of the behavior we want to verify, and which lines relate to the actual assertion?

c3/14/RetweetCollectionTest.cpp
 
TEST_F(ARetweetCollection, IgnoresDuplicateTweetAdded) {
 
Tweet tweet(​"msg"​, ​"@user"​);
 
Tweet duplicate(tweet);
 
collection.add(tweet);
 
collection.add(duplicate);
 
ASSERT_THAT(collection.size(), Eq(1u));
 
}

With the lines clumped together, it takes a few extra moments to see exactly what’s being tested. Contrast that version of the test with code that makes the Arrange, Act, and Assert portions of the test obvious.

c3/13/RetweetCollectionTest.cpp
 
TEST_F(ARetweetCollection, IgnoresDuplicateTweetAdded) {
 
Tweet tweet(​"msg"​, ​"@user"​);
 
Tweet duplicate(tweet);
 
collection.add(tweet);
 
 
collection.add(duplicate);
 
 
ASSERT_THAT(collection.size(), Eq(1u));
 
}

Some test-drivers prefer the mnemonic Given-When-Then. Given a context, When the test invokes some behavior, Then some result is verified. (The cleanup task isn’t part of the mnemonic; it’s considered housekeeping with minimal significance to an understanding of the behavior your test verifies.) You might also hear the phrase Setup—Execute—Verify (—Teardown). Given-When-Then provides a slight additional connotation that your focus when doing TDD is on verifying behavior, not executing tests. It also aligns with an analogous concept used for Acceptance Test--Driven Development (see Unit Tests, Integration Tests, and Acceptance Tests).

The concept of AAA won’t shatter any earths, but applying it consistently will eliminate unnecessary friction that haphazardly organized tests create for the reader.

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

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