Test Assertions

Most TestFrameworks? use TestAssertions. E.g.

  CPPUNIT_ASSERT( m_1_2 == m_1_2 );
  TEST_ASSERT( Complex(1,2) == Complex(1,2) );
  test_assert( m_1_2 == m_1_2 );
or even the standard
  assert( m_1_2 == m_1_2 );

It is probably best NOT to use the standard assert(). Better to define your own assert-like macro, and allow it to be #defin'd to standard assert() if you wish.

I prefer a generic name like TEST_ASSERT, rather than a specific name like CPPUNIT_ASSERT, since my tests usually are for libraries that may have to coexist with several different test suites.

Printing the Assert Condition and Coordinates

One nearly always wants to print a message like

file.cc line 25: in function test_complex_numbers: assertion "Complex(1,2) == Complex(1,2)" failed

The standard C/C++ __FILE__ and __LINE__ give you the filename and linenumber, allowing your IDE or GNU EMACS to jump to the failure.

Unfortunately C/C++ do not have a standard way of obtaining the function name, although GNU C/C++ has __FUNCTION__ and __PRETTY_FUNCTION__, and many other compilers have __func__. Note that the fully adorned name may be desirable, since simply printing "test" rather than "TestComplexAdd?.test()" is disappointing.

The final part is typically the text of the assertion condition. In C/C++ this is typically obtained by a macro like (omitting the other coordinates for simplicity):

#define TEST_ASSERT(cond){ if( !(cond) ) { printf("%s failed\n",##cond); 
} } // end macro TEST_ASSERT

As a result, it is therefore traditional for asserts to be a macro.

Unfortunately, this often produces macro wars, as the exact definition of the macro, whether it attempts to plug into a different test object, is up to question.

TestAssert? variants

Simply printing the assert condition is not always the most effective message. For example

retval_t ret = BigFunctionWithComplexReturn?();
TEST_ASSERT( ret.code == 1 );
TEST_ASSERT( ret.vale = "1234" );

Therefore, several variants of assert are somewhat common:
TEST_ASSERT_MSG( ret.code = 1, ("BigFunctionWithComplexReturn? returned %d, expected 1\n",ret.code))
The reader may recognize the common C/C++ idiom of a parameterized argument for the printf-like string.

Other variants include

TEST_ASSERT_EQUALS( foo(), 1 )
producing a message like
TEST_ASSERT_EQUALS failed: foo() == 55, expected 1

and variants for common things, like
TEST_ASSERT_RANGE( fp_number, 1.5,.16)

Although producing pleasant error messages, such variants quickly proliferate, and are not ubiquitous. They may hinder peaceful coexistence of your tests with a different test suite.

Connecting TestAssert? to the TestingFramework

The test framework probably wants to do special things with the test assertions, including

and so on.

Redefining the TEST_ASSERT macro works, but note that you only get to do this once per compilation of the object files(s) that contain the TEST_ASSERTs.

Therefore, you must arrange so that the definition of TEST_ASSERT does what you want. E.g. hardwiring output to cout rather than cerr is bad, if you may later want to have some tests that are expected to fail and whose output needs to be redirected to /dev/null.

Therefore, typically TEST_ASSERT is #defined to call

#define TEST_ASSERT(cond) {(if(!(cond)) ) tester.test_assert(__FILE__,__LINE__,##cond); }}

Coords

As you can see, the tester.test_assert() method must expect all of the arguments that are used to create meaningful coordinates. These may be factored out into another macro

#ifdef GNU_C
#  define FILE_COORDS file_coords(__FILE__,__LINE,__FUNCTION__)
#else
#  define FILE_COORDS file_coords(__FILE__,__LINE,"function name not available")
#endif

#define TEST_ASSERT(cond) {(if(!(cond)) ) tester.test_assert(FILE_COORDS,##cond); }}

but this begins the slippery slope of depending on more and more stuff.

Global tester

When TEST_ASSERT is defined to call a test object

#define TEST_ASSERT(cond) {(if(!(cond)) ) tester.test_assert(##cond); }}
the object is, unfortunately, usually a global.

Some test suites pass a tester object around, but that is framework specific. Better code, but harder to live with in a heterogeneous environment.

So, fall back to making the global a delegator, overriding, when redirection is necessary. Care must be taken to restore the old delegate after an exception - the usual C++ idiom for resource allocation applies.

The global tester object needs to have a unique and well known name. Also, there may arise initialization ordering issues. Therefore, using the C++ singleton function idiom is desirable.

class test_c {
static tester_c& delegate;
public:
virtual void set_delegate( tester_c& arg_delegate ) ... 
virtual void print( ... ) ...
};

tester_c global_tester_singleton() { static tester_c tester; return tester; }


CategoryAssertions


EditText of this page (last edited April 29, 2006) or FindPage with title or text search