Unit Testing
One important application of initializer lists is in defining cases for automated unit testing. Consider the testing of the addition operators for a new string class, xstring. Listing Two shows one way to write the tests. The ASSERT is meant to represent whatever macro or function the unit-test framework uses to express test assertions. Using initializer lists to set up a table of test data has two advantages: First, the data is clearly laid out, separate from the actual test code; and second, there is no repetition of the code. This is particularly useful when each test case needs a few lines of setup, or when the data can be combined in multiple ways to generate extra tests "for free." For example, in Listing Two, the same data is used to test five different functions: Three forms of operator+ and two of operator+=. Similarly, for most numeric types (but not strings), if a + b == c, it should also be true that b + a == c, that a == c - b, and so on.
void test_addition_operators() { struct Test_case { const char *a, *b, *sum; }; Test_case cases[] = { // a b sum { "abc", "xyz", "abcxyz" }, { "abc", "", "abc" }, { "", "xyz", "xyz" }, { "", "", "" } }; for ( int i = 0 ; i != COUNTOF(cases) ; ++i ) { Test_case& tc = cases[i]; xstring a(tc.a), b(tc.b), sum(tc.sum); ASSERT( a + b == sum ); ASSERT( a + tc.b == sum ); ASSERT( tc.a + b == sum ); a += b; // operator+=(const xstring&) ASSERT( a == sum ); a = tc.a; a += tc.b; // operator+=(const char*) ASSERT( a == sum ); } }
It may look strange to define a struct right inside a function as I did, but it's perfectly legal C++. Using a local struct has the same benefits as limiting any other definition to a narrow scope. I am only using the struct in this one function, and defining it inside the function makes that immediately clear. It also saves you from having to hunt for the definition by putting it right at the point of use. It prevents namespace pollution, and as a result, saves you from having to invent new (and usually more verbose) names for a "Test_case."
Unit tests are really just a particular example of a general idiom for writing table-driven code. When faced with a repetitious series of operations, you can often drive out the duplication by extracting just the parts that vary, putting them into a table, then looping over the rows of that table. The table could be in a database or external file when needed, but putting it directly in code gives you a powerful combination of efficiency and clarity.