The Problem
The code has a function that uses large std::string objects as a return value. When these are used in tests, the CPPUNIT_ASSERT_EQUAL macro tells you if they differ, but you have to look hard to find the difference.
Example
A function returns a string representing a tic-tac-toe board. Here is a particular test that will fail, because the test writer accidently typed a '0' (zero) rather than an 'O' (letter):
1 void TicTacToeTests::testScenarioOne()
2 {
3 std::string expected =
4 "|---|---|---|\n"
5 "| X | O | 0 |\n"
6 "|---|---|---|\n"
7 "| | X | |\n"
8 "|---|---|---|\n"
9 "| | | X |\n"
10 "|---|---|---|\n";
11 CPPUNIT_ASSERT_EQUAL(expected, run_scenario_one());
12 }
When the test fails, the text output is:
Test name: OffsetsTest::testScenarioOne equality assertion failed - Expected: |---|---|---| | X | O | 0 | |---|---|---| | | X | | |---|---|---| | | | X | |---|---|---| - Actual : |---|---|---| | X | O | O | |---|---|---| | | X | | |---|---|---| | | | X | |---|---|---|
If you made the typo the first time, it may be hard to see it this time. The problem is worse if the difference is in whitespace (tab instead of space, extra linefeed), since some compilers strip these from the output.
Solution
Use a function that creates a string describing the difference. Here are two functions that help out, plus a macro. They are:
char_to_string(c) - Prints the character (or a representation, if non-printing character), plus the ASCII code.
check_strings(expected, actual) - Returns a string identifying differences, such as:
- A mismatch in characters, including the character positions, the characters, and the next few characters in the strings
- A mismatch in string sizes, including the first extra character in the actual string (for that trailing whitespace)
ASSERT_STRINGS_EQUAL( expected, actual ) - A drop-in replacement for CPPUNIT_ASSERT_EQUAL
1 std::string
2 char_to_string(const char c)
3 {
4 static const std::string control_code[32] = {
5 /* 0 1 2 3 4 5 6 7 8 9 10*/
6 "NUL","SOH","STX","ETX","EOT","ENQ","ACK","BEL", "BS", "HT", "LF",
7 "VT", "FF", "CR", "SO", "SI","DLE","DC1","DC2","DC3","DC4","NAK",
8 "SYN","ETB","CAN", "EM","SUB","ESC", "FS", "GS", "RS"
9 };
10
11 std::ostringstream result;
12 result << "'";
13 if (c <= 31) {
14 result << control_code[c];
15 } else {
16 result << c;
17 }
18 result << "'(" << (int)c << "d)";
19 return result.str();
20 }
21
22 // Return a string describing differences between strings
23 std::string
24 check_strings(const std::string &expected, const std::string &actual)
25 {
26 if (expected == actual) return "Strings are equal";
27
28 std::ostringstream difference;
29 typedef std::string::const_iterator string_iterator;
30 std::pair<string_iterator, string_iterator> result =
31 std::mismatch(expected.begin(), expected.end(), actual.begin());
32
33 if (result.first != expected.end()) {
34 std::string::size_type pos = result.first - expected.begin();
35 difference << "Mismatch at pos. " << (int)pos <<
36 ", expected is " << char_to_string(*result.first) <<
37 ", actual is " << char_to_string(*result.second) <<
38 ".\n";
39 difference << "Expected:[" << expected.substr(pos, 40) <<
40 "]\nActual :[" << actual.substr(pos, 40) <<
41 "]\n;";
42 }
43
44 if (expected.length() < actual.length()) {
45 difference << "Actual string is longer than expected (" <<
46 (int)actual.length() << " vs. " <<
47 (int)expected.length() <<
48 "), first extra character is '" <<
49 char_to_string(actual[expected.length()]) <<
50 "'\n";
51 }
52 return difference.str();
53 }
54
55 #define ASSERT_STRINGS_EQUAL(EXPECTED, ACTUAL) \
56 { CPPUNIT_ASSERT_EQUAL_MESSAGE(check_strings(EXPECTED, ACTUAL), EXPECTED, ACTUAL); }
In the test, change the ASSERT macro used:
1 //CPPUNIT_ASSERT_EQUAL(expected, run_scenario_one());
2 ASSERT_STRINGS_EQUAL(expected, run_scenario_one());
Here is the new output for the failing test:
equality assertion failed - Expected: |---|---|---| | X | O | 0 | |---|---|---| | | X | | |---|---|---| | | | X | |---|---|---| - Actual : |---|---|---| | X | O | O | |---|---|---| | | X | | |---|---|---| | | | X | |---|---|---| - Mismatch at pos. 24, expected is '0'(48d), actual is 'O'(79d). Expected:[0 | |---|---|---| | | X | | |---|---] Actual :[O | |---|---|---| | | X | | |---|---] ;
It tells you more than you need to know to fix the problem, but that's better than not enough information, like the CPPUNIT_ASSERT_EQUALS() macro.
Discussion
I've been using this for a couple of days without writing any unit tests for it (slap! bad programmer!), but it's been too useful not to use or share. If I write some unit tests, I'll post them -- JohnWhitlock 2005-03-10 22:18:29