KUnitTest - a UnitTest library for KDE

# Contents

- Background
- Example usage
- Integration
- Creating test modules
- Advanced usage
- Scripts

# Background

KUnitTest is based on the "in reality no one wants to write tests and if it takes a lot of code no one will. So the less code to write the better" design principle.

Copyright and credits:

- (C) 2004 Zack Rusin (original author)
- Brad Hards (import into CVS)
- (C) 2005 Jeroen Wijnhout (GUI, library, module)

You are responsible for what you do with it though. It is licensed under a BSD license - read the top of each file.

All the GUI related stuff is in kdesdk/kunittest, the core libraries are in kdelibs/kunittest. A simple example modules is in kdelisbs/kunittest/samplemodule.{h,cpp}, however more examples can be found in kdesdk/kunittest/example.

There are roughly two ways to use the KUnitTest library. Either you create dynamically loadable modules and use the kunittestmodrunner or kunittestguimodrunner programs to run the tests, or you use the kunittest/kunittestgui library to create your own test runner application.

The main parts of the KUnitTest library are:

- runner.{h,cpp} - it is the tester runner, holds all tests and runs them.
- runnergui.{h,cpp} - the GUI wrapper around the runner. The GUI neatly organizes the test results. With the kunittest helper script it can even add the debug output to the test results. For this you need to have the kdesdk module installed.
- tester.h - which holds the base of a pure test object (Tester).
- module.h - defines macros to create a dynamically loadable module.

# Example usage

This section describes how to use the library to create your own tests and runner application.

Now lets see how you would add a new test to KUnitTest. You do that by writting a Tester derived class which has an "allTests()" method:

class SampleTest : public Tester
{
public:
SampleTest();
void allTests();
};

Now in the allTests() method we implement our tests, which might look like:

void SampleTest::allTests()
{
CHECK( 3+3, 6 );
CHECK( QString( "hello%1" ).arg( " world not" ), QString( "hello world" ) );
}

CHECK() is implemented using a template, so you get type safe comparison. All that is needed is that the argument types have an `operator==()` defined.

Now that you did that the only other thing to do is to tell the framework to add this test case, by using the KUNITTEST_REGISTER_TESTER(x) macro. Just put the following line in the implementation file:

KUNITTEST_REGISTER_TESTER( SampleTest );

Note the ;, it is necessary.

KUnitTest will do the rest. It will tell you which tests failed, how, what was the expected result, what was the result it got, what was the code that failed and so on. For example for the code above it would output:

    SampleTest - 1 test passed, 1 test failed
        Unexpected failure:
            sampletest.cpp[38]: failed on "QString( "hello%1" ).arg( " world not" )"
                result = 'hello world not', expected = 'hello world'

If you use the RunnerGUI class then you will be presented with a scrollable list of the test results.

# Integration

The KUnitTest library is easy to use. Let's say that you have the tests written in the sampletest.h and sampletest.cpp files. Then all you need is a main.cpp file and a Makefile.am. You can copy both from the example file provided with the library. A typical main.cpp file looks like this:

#include <kaboutdata.h>
#include <kapplication.h>
#include <kcmdlineargs.h>
#include <kcmdlineargs.h>
#include <klocale.h>
#include <kunittest/runnergui.h>
static const char description[] = I18N_NOOP("SampleTests");
static const char version[] = "0.1";
static KCmdLineOptions options[] = { KCmdLineLastOption };
int main( int argc, char** argv )
{
KAboutData about("SampleTests", I18N_NOOP("SampleTests"), version, description,
KAboutData::License_BSD, "(C) 2005 You!", 0, 0, "mail@provider");
KCmdLineArgs::init(argc, argv, &about);
KCmdLineArgs::addCmdLineOptions( options );
KApplication app;
KUnitTest::RunnerGUI runner(0);
runner.show();
app.setMainWidget(&runner);
return app.exec();
}

The Makefile.am file will look like:

INCLUDES = -I$(top_srcdir)/src $(all_includes)
METASOURCES = AUTO
check_PROGRAMS = sampletests
sampletests_SOURCES = main.cpp sampletest.cpp
sampletests_LDFLAGS = $(KDE_RPATH) $(all_libraries)
sampletests_LDADD = -lkunittest
noinst_HEADERS = sampletest.h
check:
kunittest $(top_builddir)/src/sampletests SampleTests

Most of this Makefile.am will be self-explanatory. After running "make check" the binary "sampletests" will be built. The reason for adding the extra make target "check" is that you probably do not want to rebuild the test suite everytime you run make.

You can run the binary on its own, but you get more functionality if you use the kunittest helper script. The Makefile.am is set up in such a way that this helper script is automatically run after you do a "make check". This scripts take two arguments, the first is the path to the binary to run. The second the application name, in this case SampleTests. This name is important since it is used to let the script communicate with the application via DCOP. The helper scripts relies on the Perl DCOP bindings, so these need to be installed.

# Creating test modules

If you think that writing your own test runner if too much work then you can also use the kunittestermodrunner application or the kunitguimodrunner script to run the tests for you. You do have to put your tests in a dynamically loadable module though. Fortunately KUnitTest comes with a few macros to help you do this.

First the good news, you don't have to change the header file sampletest.h. However, we will rename it to samplemodule.h, so we remember we are making a module. The implementation file should be rename to samplemodule.cpp. This file requires some modifications. First we need to include the module.h header:

#include <kunittest/module.h>

This header file is needed because it defines some macro you'll need. In fact this is how you use them:

KUNITTEST_MODULE( kunittest_samplemodule, "Tests for sample module" );
KUNITTEST_MODULE_REGISTER_TESTER( SimpleSampleTester );
KUNITTEST_MODULE_REGISTER_TESTER( SomeSampleTester );

The first macro, KUNITTEST_MODULE(), makes sure that the module can be loaded and that the test classes are created. The first argument "kunittest_samplemodule" is the library name, in this case the library we're creating a kunittest_samplemodule.la module. The second argument is name which will appear in the test runner for this test suite.

The tester class are now added by the KUNITTEST_MODULE_REGISTER_TESTER() macro, not the KUNITTEST_REGISTER_TESTER(). The only difference between the two is that you have to pass the module class name to this macro.

The Makefile.am is also a bit different, but not much:

INCLUDES = -I$(top_srcdir)/include $(all_includes)
METASOURCES = AUTO
check_LTLIBRARIES = kunittest_samplemodule.la
kunittest_samplemodule_la_SOURCES = samplemodule.cpp
kunittest_samplemodule_la_LIBADD = $(LIB_KUNITTEST)
kunittest_samplemodule_la_LDFLAGS = -module $(KDE_CHECK_PLUGIN) $(all_libraries)

The macro is there to make sure a dynamically loadable module is created.

After you have built the module you open a Konsole and cd into the build folder. Running the tests in the module is now as easy as:

$ make check && kunittestmodrunner

The kunittestmodrunner application loads all kunittest_*.la modules in the current directory. The exit code of this console application is the number of unexpected failures.

If you want the GUI, you should use the kunittestmod script:

$ make check && kunittestmod

This script starts kunittestguimodrunner application and a helper script to take care of dealing with debug output.

# Advanced usage

Normally you just want to use CHECK(). If you are developing some more tests, and they are run (or not) based on some external dependency, you may need to skip some tests. In this case, rather than doing nothing (or worse, writing a test step that aborts the test run), you might want to use SKIP() to record that. Note that this is just a logging / reporting tool, so you just pass in a string:

SKIP( "Test skipped because of lack of foo support." );

Similarly, you may have a test step that you know will fail, but you don't want to delete the test step (because it is showing a bug), but equally you can't fix it right now (eg it would break binary compatibility, or would violate a string freeze). In that case, it might help to use XFAIL(), for "expected failure". The test will still be run, and recorded as a failure (assuming it does fail), but will also be recorded separately. Usage might be as follows:

XFAIL( 2+1, 4 );

You can mix CHECK(), SKIP() and XFAIL() within a single Tester derived class.

# Exceptions

KUnitTest comes with simple support for testing whether an exception, such as a function call, throws an exception or not. Simply, for the usual macros there corresponding ones for exception testing: CHECK_EXCEPTION(), XFAIL_EXCEPTION(), and SKIP_EXCEPTION(). They all take two arguments: the expression that will catch the exception, and the expression that is supposed to throw the exception.

For example:

CHECK_EXCEPTION(EvilToothFairyException *, myFunction("I forgot to brush my teeth!"));

Note
  The exception is not de-allocated in anyway.

The macros does not allow introspection of the exceptions, such as testing a supplied identifier code on the exception object or similar; this requires manual coding, such as custom macros.

# Scripts

The library comes with several helper scripts:

- kunittest [app] [dcopobject] : Runs the application app and redirects all debug output to the dcopobject.
- kunittestmod –folder [folder] –query [query] : Loads and runs all modules in the folder matching the query. Use a GUI.
- kunittest_debughelper [dcopobject] : A PERL script that is able to redirect debug output to a RunnerGUI instance.

These scripts are part of the kdesdk/kunittest module.

KDE Home | KDE Accessibility Home | Description of Access Keys