Contents
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 tdesdk/tdeunittest, the core libraries are in tdelibs/tdeunittest. A simple example modules is in kdelisbs/tdeunittest/samplemodule.{h,cpp}, however more examples can be found in tdesdk/tdeunittest/example.
There are roughly two ways to use the KUnitTest library. Either you create dynamically loadable modules and use the tdeunittestmodrunner or tdeunittestguimodrunner programs to run the tests, or you use the tdeunittest/tdeunittestgui 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 tdeunittest helper script it can even add the debug output to the test results. For this you need to have the tdesdk 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:
Now in the allTests() method we implement our tests, which might look like:
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 TDEUNITTEST_REGISTER_TESTER(x) macro. Just put the following line in the implementation file:
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 "TQString( "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:
The Makefile.am file will look like:
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 tdeunittest 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 tdeunittestermodrunner 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:
This header file is needed because it defines some macro you'll need. In fact this is how you use them:
The first macro, TDEUNITTEST_MODULE(), makes sure that the module can be loaded and that the test classes are created. The first argument "tdeunittest_samplemodule" is the library name, in this case the library we're creating a tdeunittest_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 TDEUNITTEST_MODULE_REGISTER_TESTER() macro, not the TDEUNITTEST_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:
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:
The tdeunittestmodrunner application loads all tdeunittest_*.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 tdeunittestmod script:
This script starts tdeunittestguimodrunner 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:
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:
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:
- 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:
- tdeunittest [app] [dcopobject] : Runs the application app and redirects all debug output to the dcopobject.
- tdeunittestmod –folder [folder] –query [query] : Loads and runs all modules in the folder matching the query. Use a GUI.
- tdeunittest_debughelper [dcopobject] : A PERL script that is able to redirect debug output to a RunnerGUI instance.
These scripts are part of the tdesdk/tdeunittest module.