One Assert per Test

You test-drive small, discrete bits of behavior into your system. With each pass through the TDD cycle, you specify behavior plus a way to verify that the behavior actually works—an assertion.

To allow a future programmer to understand the behavior you designed into the system, your test must clearly state intent. The most important declaration of intent is the test’s name, which should clarify the context and goal.

The more behavior you drive from a single test, the less your test name can concisely describe the behavior.

In the library system, a holding is unavailable when a patron has checked it out and is available once a patron has checked it in. We might design a single test focused on availability.

c7/3/libraryTest/HoldingTest.cpp
 
TEST_F(HoldingTest, Availability)
 
{
 
holding->Transfer(EAST_BRANCH);
 
holding->CheckOut(ArbitraryDate);
 
EXPECT_FALSE(holding->IsAvailable());
 
 
date nextDay = ArbitraryDate + date_duration(1);
 
holding->CheckIn(nextDay, EAST_BRANCH);
 
EXPECT_TRUE(holding->IsAvailable());
 
}

Combining behaviors into a single test puts them all into the same place—sort of. You’ll likely find that many of your methods work in concert with other methods, meaning that it can be a real challenge to figure out to which method-focused test the test code belongs. The downside is that it will take additional time for a reader to understand what’s going on, particularly as you add a third, fourth, or umpteenth behavior to the test.

Splitting into multiple tests allows you to derive names that clearly state what happens under what conditions. It also allows you to take advantage of Arrange-Act-Assert/Given-When-Then for increased clarity.

c7/3/libraryTest/HoldingTest.cpp
 
TEST_F(AHolding, IsNotAvailableAfterCheckout)
 
{
 
holding->Transfer(EAST_BRANCH);
 
 
holding->CheckOut(ArbitraryDate);
 
 
EXPECT_THAT(holding->IsAvailable(), Eq(false));
 
}
 
 
TEST_F(AHolding, IsAvailableAfterCheckin)
 
{
 
holding->Transfer(EAST_BRANCH);
 
holding->CheckOut(ArbitraryDate);
 
 
holding->CheckIn(ArbitraryDate + date_duration(1), EAST_BRANCH);
 
 
EXPECT_THAT(holding->IsAvailable(), Eq(true));
 
}

The names of single-purpose tests stand on their own—you don’t have to read a test to understand what it does. The complete set of such test names acts as a concordance of system capabilities. You start to view test names as related groupings of behaviors, not just discrete verifications.

Looking at test names holistically can trigger thoughts about missing tests. “We have library tests that describe availability on checkout and check-in. What holds true about availability when we’ve added a new book to the system through an inventory process? We’d better write that test!”

Should you ever have more than one assert per test? Strive hard to have only one. Sometimes it makes sense to have more, though.

Assertions are postconditions. If multiple assertions are required to describe a single behavior, you can justify a second assertion in a test. Consider the method IsEmpty, often added to increase expressiveness beyond what a function like Size produces. You might choose to involve both functions as postcondition tests around emptiness of a new collection.

You might also choose to have multiple assertions to verify a bunch of data elements.

c7/3/libraryTest/HoldingTest.cpp
 
TEST_F(AHolding, CanBeCreatedFromAnother)
 
{
 
Holding holding(THE_TRIAL_CLASSIFICATION, 1);
 
holding.Transfer(EAST_BRANCH);
 
 
Holding copy(holding, 2);
 
 
ASSERT_THAT(copy.Classification(), Eq(THE_TRIAL_CLASSIFICATION));
 
ASSERT_THAT(copy.CopyNumber(), Eq(2));
 
ASSERT_THAT(copy.CurrentBranch(), Eq(EAST_BRANCH));
 
ASSERT_TRUE(copy.LastCheckedOutOn().is_not_a_date());
 
}

Finally, you might combine assertions where the description of behavior doesn’t vary but the implementation is getting more specialized as more data variants are added. For example, this utility converts Arabic numbers to Roman numbers:

c7/3/libraryTest/RomanTest.cpp
 
TEST(ARomanConverter, AnswersArabicEquivalents)
 
{
 
RomanConverter converter;
 
ASSERT_EQ(​"I"​, converter.convert(1));
 
ASSERT_EQ(​"II"​, converter.convert(2));
 
ASSERT_EQ(​"III"​, converter.convert(3));
 
ASSERT_EQ(​"IV"​, converter.convert(4));
 
ASSERT_EQ(​"V"​, converter.convert(5));
 
// ...
 
}

You could choose to split tests in either of these cases. But having separate tests doesn’t appear to have as much value, evidenced by the names you might come up with: ConvertsRomanIIToArabic, ConvertsRomanIIIToArabic, and so on. Or CopyPopulatesClassification, CopyPopulatesCopyNumber, and so on.

The key thing to remember is to have one behavior per test. And in case it’s not obvious, any test with conditional logic (for example, if statements) is almost certainly violating this guideline.

One Assert per Test isn’t a hard rule, but it’s usually a better choice. Head in the direction of fewer assertions per test, not more. The more you do it, the more you’ll find the value. For now, strive for a single assert per test and contemplate the results.

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

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