-list
and
-debug
FAIL
Sometimes, you want to write several tests that differ only in the values of some parameters. In this case, you can pass the parameters as arguments to its constructor; you can specify their values as non-template arguments when adding the test to the suite. Here's an example:
class Square { public: Square(int toSquare, int expected) : toSquare_(toSquare), expected_(expected) { } void run() { ASSERT_EQUALS(expected_, toSquare * toSquare); } private: const int toSquare_; const int expected_; }; class All : public UnitTest::Suite { public: All() { add<Square>(0, 0); add<Square>(1, 1); add<Square>(2, 4); } };
This can get a little verbose (as in this example); if that happens, you can sometimes make the test class itself a template class, with the parameters template arguments instead of constructor arguments.
-list
and
-debug
As we mentioned before, if you
only want to run tests from some of the suites, you can pass the
names of the suites as arguments to your test programs. If
you've forgotten the names of the suites, however, you can pass
the argument -list
to your test program. It will
print out the names of your suites and exit.
If, for some reason, you need to know when each test starts and
finishes, you can pass the argument -debug
to your
test program. It will print out the name of each test as it
gets constructed, run, and destroyed.
If you add an suite which doesn't contain any tests, your program will report one pass and no failures. The reason for this is that each suite counts as a test.
Similarly, if you add a suite which contains one failing test, then your program will report one pass and one failure. The failure is from the failing test; the pass is from the suite. The suite passed because it did its job, namely to run the tests contained therein; the fact that the test failed doesn't mean that the suite failed.
If, for some reason, the suite has a problem in its constructor or destructor, however, the suite will be marked as failing.
If your test causes certain signals (seg fault, divide by zero, abort) to be raised, then the framework will catch that signal print out an appropriate message, and mark the test as having failed. It will in addition print out a backtrace of where the signal occurred (at least under Linux). That backtrace isn't always very helpful so, much of the time, it's easier to simply run your test program under GDB.
In this situation, the test framework will attempt to clean up
your test and continue as normal, but it will sometimes be
unable to do so. In particular, the recovery mechanism is
handled via setjmp/longjmp
instead of exceptions,
so not all objects' destructors may be called normally. It will
attempt to call your test's destructor, however.
If an ASSERT_EQUALS
fails, it prints out the actual
and expected values. If the values in question are integers, it
will print them out in decimal by default. If you wish to see
the values in hex, you can do the following:
UnitTest::radix(UnitTest::HEX);
Sometimes, when an assertion fails, you want to be able to poke
at the environment in a debugger. Behind the scenes, assertions
are implemented via exceptions; if you're lucky, doing
catch throw
in GDB will let you know when the
corresponding exception is thrown.
If your code throws a lot of exceptions, however, it might be
hard to pick out the right one. In this case, the natural thing
to do would be to set a breakpoint on the assertion exception
class's constructor. Unfortunately, as of this writing, setting
breakpoints in constructors doesn't reliably work in GDB. To
work around this, we've provided a function
UnitTest::AssertionFailure::triggered
which the
constructor calls; you can set a breakpoint there instead.
FAIL
Assertions are used to detect problems discovered by your test's
run()
function. You can also use them in your
test's constructor, if for some reason you want to do that as a
sanity check. (It's usually a sign of bad design, however.)
Sometimes, however, you want to signal that a problem occurred in a test's or destructor. (Normally, this is a sign of problems in your test design; the main situation where this happens naturally is in the destructor of mixin classes.) In this situation, using assertions is an extremely bad idea: they're implemented as exceptions, and, for various good reasons, throwing exceptions in a destructor is very bad form.
If you detect a problem in a destructor, therefore, you should instead callFAIL()
, passing it an appropriate
string as an argument. The rest of the destructor will run as
normal (as opposed to an assertion failure, which immediately
halts the run()
function), but the test will be
marked as having failed, even if no assertion was triggered.
(If both an assertion and a FAIL()
trigger,
however, only the assertion message will be printed.)
You can also call FAIL()
during the running of a
test. (Or during the constructor, but I can't think of a reason
why you'd want to do that.) There's rarely a need to do so; one
case when it could come in handy is when testing multithreaded
code. You can't do an assertion in a different thread from the
one in which run()
is being called, but you can
call FAIL()
from any thread. (The implementation
isn't thread-safe, but you're unlikely to have multiple threads
simultaneously calling FAIL()
anyways.)
When using this framework, it's helpful to not only follow good TDD style, as practiced in other languages, but also to follow good C++ style. In particular, the "Resource Acquisition Is Initialization" design pattern is very helpful. If several tests within a suite want the same sort of setup and teardown, you can pull that to a base class's constructor and destructor. If several tests across different suites want to do a similar sort of setup or teardown, however, that's a sign that you should abstract out a helper class (or an "RAII class") whose only job is to provide a constructor and destructor to do the setup and teardown. Your different tests in different suites can then all have member variables whose type is that RAII class.
In general (not just when writing tests, but when writing any
sort of C++ code), you should look on destructors with
suspicion: whenever a destructor is doing something, you should
ask yourself whether that functionality should be moved out into
an RAII class. For example, it's rarely good form for a
destructor to call delete
- instead, your class
should have a member variable whose type is
std::auto_ptr
(or some other type of smart
pointer). Doing so can simplify your code significantly, and
makes it much easier to write robust, exception-safe code.
The text on this page may be copied and distributed, in modified or unmodified form, by anybody for any purpose.