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:

   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


CategoryTips

TestingStrings (last edited 2008-02-26 06:29:55 by localhost)

SourceForge.net Logo