Creating StandAlone Executable Tests

 

Compiled-code tests written in C++ are capable of testing anything in Mozilla, making them perhaps the most powerful testing method available. However this power comes with a price, for these tests can be difficult to write correctly. For web-visible testing, you are nearly always better off with either a Mochitest or a reftest; for unit testing, you should write an xpcshell test if the functionality being tested is available through scriptable methods and interfaces.

If the functionality you wish to test is only available to compiled code, however, a compiled-code test is the right choice.

The structure of a compiled-code test

Using XPCOM requires including a number of headers and writing some tedious initialization code, under normal circumstances. For a test, however, most of this nonsense can be eliminated by including the test-support header xpcom/tests/TestHarness.h. This header provides a scoped-XPCOM class, the smart pointer classes, the string classes, do_CreateInstance, and stdio.h/stdlib.h. With that, the basic structure of a compiled-code test is reduced to the following:

// standard Mozilla tri-license

#include "TestHarness.h"

nsresult TestFoo()
{
  if (1 == 1)
  {
    fail("why isn't 1 == 1?");
    return NS_ERROR_FAILURE;
  }

  // do your stuff and either fail in there or fall through
  passed("TestFoo");
  return NS_OK;
}

nsresult TestOtherFoo()
{
  passed("TestOtherFoo");
  return NS_OK;
}

int main(int argc, char** argv)
{
  ScopedXPCOM xpcom("FooTests"); // name for tests being run
  if (xpcom.failed())
    return 1;

  // XPCOM is now started up, and you can access components/services/etc.

  int rv = 0;

  if (NS_FAILED(TestFoo()))
    rv = 1;
  if (NS_FAILED(TestOtherFoo()))
    rv = 1;
  // ...

  return rv; // failure is a non-zero return
}

If you're fixing bugs in compiled code, you can probably figure out how to fill out the rest of this skeleton.

There are a couple things to note that aren't demonstrated by this skeleton. First, note that a test failure doesn't cause the test to immediately fail. While it's possible to write tests that fail as early as possible, usually it's possible to run multiple tests and only report failure at the end, which can save the frustrated developer who breaks your test the trouble of fixing each failure individually. Second, a test which fails should do so by printing an error message using fail() and returning a failure code. When possible, the error should be printed as early as possible and propagated up the call stack without printing further errors (note how in the example the TestFoo() doesn't have a corresponding printed message, because TestFoo's definition should do so before returning a failure code), but if an error must be printed multiple times, that's fine. Use the fail() and passed() functions in the test harness provides to report errors. Third, it's fine to print out pass messages using the passed() function at the end of tests, as occurs at the end of TestFoo(), but if you're printing more than 10-15 you should consider being more selective in what you print. The intent is to give information about test progress, not to overwhelm the screen or tinderbox logs.  (Yes, I'm looking at you, netwerk/test/TestCookie.cpp!)

Once you've written your test, you need to hook it up to the build system: making sure xpcom/tests/TestHarness.h is found correctly; linking against NSPR, XPCOM, and XPCOM glue libraries; and making it run automatically with fatal assertions when make check is invoked. You can do this by adding the following line to the Makefile.in in the directory containing your test (assuming ENABLE_TESTS is already defined):

CPP_UNIT_TESTS = TestFoo.cpp

Yes, Virginia, there is a Santa Claus who makes hooking compiled-code tests up to the build system super-easy.

You can hook up any other headers you need through the standard methods like REQUIRES. Note that because your test will be run in non-debug trees and tinderboxen, you should ensure your code builds in both debug and optimized configurations so that you don't rely on non-exported symbols libxul hides from you.

Functionality provided by TestHarness.h

The ScopedXPCOM class is the most useful thing provided; the functionality it provides is minimal enough (and is fully demonstrated above) that just reading the header is easier than describing it.  Helper functions (demonstrated in the example above) for standardizing error reporting are also provided.

Running the tests

Just run them using the standard make -C $OBJDIR/path/to/containing/folder check command.

Python unit tests

In addition to compiled C++ tests, make check also invokes python unit tests files throughout the tree and denoted via the PYTHON_UNIT_TESTS variable in any of the tree's Makefile.in files. Not to be confused with the unittest module in python's standard library, the tests invoked by make check are python scripts given as paths relative to the location of the refering Makefile.in in the tree with $(PYTHON) available to rules.mk . They do not have to be definitionally unit tests, but they do have to exit with 0 on success and non-zero on failure in the standard fashion. These tests mostly ensure the relative sanity of various aspects of the tree and its infrastructure (e.g. build system, infrastrucutre).

The (minimalist) code that invokes the python unit tests is rules.mk at https://hg.mozilla.org/mozilla-central/file/2cc710018b14/config/rules.mk#l136 with supporting code elsewhere in that file.  For examples, see http://mxr.mozilla.org/mozilla-central/search?string=PYTHON_UNIT_TESTS .

Examples

 

Document Tags and Contributors

Contributors to this page: Kray2, Waldo
Last updated by: Waldo,