You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
475 lines
16 KiB
475 lines
16 KiB
<chapter id="unit-test">
|
|
<title>Unit Testing</title>
|
|
<para>
|
|
If this project handbook would have been for a professional project (with
|
|
professional I mean, a project that people make money with), I would have
|
|
written
|
|
</para>
|
|
|
|
<caution>
|
|
<para>
|
|
<emphasis>Unit tests must be supplied by the developer
|
|
with the classes/source-code he checks into the repository!</emphasis>.
|
|
</para>
|
|
</caution>
|
|
|
|
<para>
|
|
Since this is
|
|
the handbook for a voluntary work (which is not less professional than any
|
|
other project), I replace the above sentence with
|
|
</para>
|
|
|
|
<note>
|
|
<para>
|
|
<emphasis>Each developer in this project is strongly encouraged to develop
|
|
unit tests for the code he or she develops and make them available to
|
|
the project team!</emphasis>.
|
|
</para>
|
|
</note>
|
|
|
|
<sect1 id="why-unit-testing">
|
|
<title>Why unit testing?</title>
|
|
|
|
<para>
|
|
Before I can give an answer to this question, I should explain what unit
|
|
testing is about. I do not cover all relevant aspects here nor do I start a
|
|
discussion of the various aspects of unit testing. If you want to read more
|
|
about the details of unit testing, the philosophy behind it and about the
|
|
various tools available, please visit the project pages of JUnit and
|
|
<ulink url="http://cppunit.sourceforge.net/">CPPUNIT</ulink>.
|
|
The following explanation describes what unit testing is:
|
|
</para>
|
|
|
|
<para>
|
|
For each class developed in a project, an accompanying test container
|
|
is developed when the interface of
|
|
the class is defined but before the implementation of the class starts. The
|
|
test container consists out of testcases that perform all necessary tests
|
|
on the class while verifying the results. One or more of these test
|
|
containers (for more than one class) form a test suite.
|
|
</para>
|
|
|
|
<para>
|
|
Your might think, that it is strange to first define the interface, then
|
|
develop the tests and then start the development of the actual code, but it
|
|
has shown, that this approach has a couple of interesting side-effects:
|
|
|
|
<itemizedlist>
|
|
<listitem>
|
|
<para>
|
|
The developer spends time to think about how to test his implementation
|
|
before he actually works on the implementation. This leads to the fact,
|
|
that while working on the implementation, he already knows how his code
|
|
will be tested.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
A clear definition of the <emphasis>end of implementation</emphasis> exists
|
|
due to the fact, that the testcases will all fail before the beginning of
|
|
the implementation phase. Once implementation proceeds, more and more
|
|
testcases will pass. When they all pass, the development is finished.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Since the tests will run automated and will be re-run very often during the
|
|
development cycle, a lot of problems will be caught very early on. This
|
|
reduces the number of problems found during integration of the project.
|
|
Believe me, there will be plenty left!
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
|
|
<para>
|
|
Now, the list of all these side-effects is actually the answer to the
|
|
question <emphasis>Why unit testing?</emphasis> or does anyone have a
|
|
argument against it? I agree, that in some cases automated unit testing is
|
|
hard to achieve (e.g. for GUI related code) but I found, that whenever it
|
|
is possible to introduce automated unit tests, the benefit is huge.
|
|
</para>
|
|
</sect1>
|
|
|
|
<sect1 id="unit-testing-in-kmm">
|
|
<title>Unit testing in &app;</title>
|
|
<para>
|
|
Just about the time, when the &app; project underwent a radical change of
|
|
it's inner business logic (the KMyMoney engine), I read an article about
|
|
the existance of a unit test container for C++ projects named
|
|
<ulink url="http://cppunit.sourceforge.net/">CPPUNIT</ulink>.
|
|
In
|
|
discussions with my colleagues at work, I got the impression, that this
|
|
would be something worth to look into. So I sat down and wrote the first
|
|
test cases for existing code to get a feeling for what is required.
|
|
</para>
|
|
|
|
<para>
|
|
I found it annoying to write test cases for code that existed and was
|
|
believed to work (version 0.4 of the project). When the decission was made
|
|
to start with the 0.5 development branch, I started working on the new
|
|
engine code that should introduce a clear interface between the business
|
|
logic and the user interface. Another design goal was to write the engine
|
|
in such a way, that it is not based on any KDE code which the old one was.
|
|
The second goal to free it from Qt based code was not that easy and was
|
|
skipped by the project team at that time.
|
|
</para>
|
|
|
|
<para>
|
|
Even if it was hard for me at first to follow the above laid out principle
|
|
to design the interface, write the test code and then start with the
|
|
implementation, I followed this trail. It has proven to be very valuable.
|
|
Once the interface was designed, I started thinking in a different manner:
|
|
How can I get this class to fail? What strange things could I do to the
|
|
code from the outside? Those were the design drivers for the test code. And
|
|
in fact, this thinking changed the way I actually implemented the code, as
|
|
I knew there was something that would check all these things over and over
|
|
again automatically.
|
|
</para>
|
|
|
|
<para>
|
|
A lot of code was implemented and when I was almost done with the first
|
|
shot of the implementation, discussion came up on the developers mailing
|
|
list about a feature called <emphasis>double entry accounting</emphasis>
|
|
that was requested for &app; by a few people. The engine I wrote up to that
|
|
point in time did not cover the aspects of double entry accounting at all,
|
|
though a few things matched. After some time of discussions, we became a
|
|
better understanding of the matter and I changed the code to cover double
|
|
entry accounting. Some of the classes remained as they were, others had to
|
|
be adopted and yet others rewritten entirely. The testcode had to be
|
|
changed as well due to the change in the interfaces, but not the logic
|
|
of the tests. Most of the thoughts how to uncover flaws remained.
|
|
</para>
|
|
|
|
<para>
|
|
And that is another reason, why unit testing is so useful: You can change
|
|
your internal implementation and still get a feeling, if your code is
|
|
working or not. And believe me: even if some changes are small, one usually
|
|
oversees a little side-effect here and there. If one has good unit tests
|
|
this is not a problem anymore, as those side-effects will be uncovered and
|
|
tested.
|
|
</para>
|
|
|
|
<para>
|
|
During the course of implementing the engine, I wrote more than 100
|
|
testcases. Each testcase sets up a testenvironment for the class and tests
|
|
various parameters against the class' methods in this environment in so
|
|
called test steps.
|
|
Exceptions are also tested to be thrown. The testcases handle unexpected
|
|
exceptions as well as expected exceptions that do not occur.
|
|
</para>
|
|
</sect1>
|
|
|
|
<sect1 id="unit-testing-howto">
|
|
<title>Unit testing HOWTO</title>
|
|
<para>
|
|
This section of the developer handbook should give some examples on how to
|
|
organize test cases and how to setup a test environment.
|
|
</para>
|
|
|
|
<para>
|
|
My examples will all be based on the code of the &app; engine found in the
|
|
subdirectory <command>kmymoney2/kmymoney2/mymoney</command> and it's
|
|
subdirectory <command>storage</command>. A
|
|
single executable exists that contains all the test code for the engine.
|
|
It's called <command>autotest</command> and resides in the mymoney
|
|
subdirectory.
|
|
</para>
|
|
|
|
<sect2 id="unit-test-integration">
|
|
<title>Integration of CPPUNIT into the &app; project</title>
|
|
<para>
|
|
The information included in the following is based on version 1.8.0 of
|
|
CPPUNIT. The &app; build system has been enhanced to check for it's
|
|
presence. Certain definitions setup by
|
|
<emphasis>automake/configure</emphasis> allow to compile the project
|
|
without unit testing support.
|
|
<caution>
|
|
<para>
|
|
This is not the recommended way for developers!
|
|
</para>
|
|
</caution>
|
|
</para>
|
|
|
|
<para>
|
|
If code within test environments is specific to the presence of CPPUNIT it
|
|
can be included in the following #ifdef primitive:
|
|
|
|
<screen>
|
|
|
|
#ifdef HAVE_LIBCPPUNIT
|
|
// specific code that should only be compiled,
|
|
// if CPPUNIT >= 1.8.0 is present
|
|
#endif
|
|
|
|
|
|
</screen>
|
|
For an example see the
|
|
<link linkend="test-container-example">Unit Test Container Source File
|
|
Example</link>.
|
|
</para>
|
|
|
|
<para>
|
|
The same applies for directives that are used in
|
|
<command>Makefile.am</command> files. The primitive to be used there is as
|
|
follows:
|
|
|
|
<screen>
|
|
|
|
if CPPUNIT
|
|
|
|
# include automake-directives here, that should be evaluated
|
|
# only, when CPPUNIT is present
|
|
|
|
else
|
|
|
|
# include automake directives here, that should be evaluated
|
|
# only, when CPPUNIT is not present.
|
|
|
|
endif
|
|
|
|
|
|
</screen>
|
|
For an example see <command>kmymoney2/mymoney/Makefile.am</command>.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="unit-test-naming">
|
|
<title>Naming conventions</title>
|
|
<para>
|
|
The test containers are also classes. Throughout CPPUNIT, the test
|
|
containers are referred to as <emphasis>test fixtures</emphasis>. In the
|
|
following, I use both terms.
|
|
For a given class <emphasis>MyMoneyAbc</emphasis>, which
|
|
resides in the files <command>mymoneyabc.h</command> and
|
|
<command>mymoneyabc.cpp</command>,
|
|
the test container is named <emphasis>MyMoneyAbcTest</emphasis> and resides
|
|
in the files
|
|
<command>mymoneyabctest.h</command> and
|
|
<command>mymoneyabctest.cpp</command> in the same directory.
|
|
The test container must be derived
|
|
publicaly from <command>CppUnit::TestFixture</command>.
|
|
Each testcase is given a
|
|
descriptive name (e.g. EmptyConstructor) and I found it useful to prefix
|
|
this name with the literal 'test' resulting into something like
|
|
testEmptyConstructor.
|
|
</para>
|
|
|
|
</sect2>
|
|
<sect2 id="unit-test-includes">
|
|
<title>Necessary include files</title>
|
|
<para>
|
|
In order to use the functionality provided by CPPUNIT, one has to include
|
|
some information provided with CPPUNIT in the test environment. This is
|
|
done with the following include primitive as one of the first things in the
|
|
header file of the test case container (e.g. mymoneyabctest.h):
|
|
|
|
<screen>
|
|
|
|
#include <cppunit/extensions/HelperMacros.h>
|
|
|
|
</screen>
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="unit-test-private">
|
|
<title>Accessing private members</title>
|
|
<para>
|
|
For the verification process it is sometimes necessary to look at some
|
|
internal states of the object under test. Usually, all this information is
|
|
declared private in the class and only accessible through setter and getter
|
|
methods. Cases exist, where these methods are not implemented on purpose
|
|
and thus accessing the information from the test container is not possible.
|
|
</para>
|
|
|
|
<para>
|
|
Various mechanism have been developed all with pros and cons. Throughout
|
|
the test containers I wrote, I used the method of redefining the specifier
|
|
<emphasis>private</emphasis> through <emphasis>public</emphasis> but only
|
|
for the time when reading the header file of the object under test. This can
|
|
easily be done by the C++ preprocessor. The following example shows how to
|
|
do this:
|
|
|
|
<screen>
|
|
|
|
#define private public
|
|
#include "mymoneyabc.h"
|
|
#undef private
|
|
|
|
|
|
</screen>
|
|
|
|
The same applies to protected members. Just add a line containing
|
|
<emphasis>#define protected public</emphasis> before including the class
|
|
definition and a line containing <emphasis>#undef protected</emphasis>
|
|
right after the inclusion line.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="unit-test-methods">
|
|
<title>Standard methods for each testcase</title>
|
|
<para>
|
|
Three methods must exist for each test fixture. These are a default
|
|
constructor, setUp and tearDown. I think, it is not necessary to explain
|
|
the default constructor here. SetUp and tearDown have a special function
|
|
within the test cases. setUp() will be called before the execution of any
|
|
test case in the test fixture. tearDown() will be called after the execution
|
|
of the test case, no matter if the test case passes or fails. Thus setUp()
|
|
is used to perform initialization necessary for each test case in the
|
|
fixture and tearDown() is used to clean things up. setUp() and tearDown()
|
|
should be written in such a way, that all objects created
|
|
through a test case should be removed by tearDown(), i.e. the environment
|
|
is restored exactly to the state it was before the call to setUp().
|
|
|
|
<note>
|
|
<para>
|
|
This is not always the case within the testcase for &app;. Espacially when
|
|
using a database as the permanent storage things have to be overhauled for
|
|
e.g. MyMoneyFileTest.
|
|
</para>
|
|
</note>
|
|
|
|
</para>
|
|
|
|
<para>
|
|
CPPUNIT comes with a set of macros that help writing testcases. I cover
|
|
them here briefly. If you wish a more detailed description, please visit
|
|
the
|
|
<ulink url="http://cppunit.sourceforge.net/">CPPUNIT</ulink> project
|
|
homepage.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="test-macro-assert">
|
|
<title>CPPUNIT_ASSERT</title>
|
|
<para>
|
|
This is the macro used at most throughout the test cases. It checks, that a
|
|
given assumption is true. If it is not, the test case fails and a
|
|
respective message will be printed at the end of the testrun.
|
|
</para>
|
|
|
|
<para>
|
|
CPPUNIT_ASSERT has a single argument which is a boolean expression. The
|
|
expression must be true in order to pass the test. If it is false, the test
|
|
case fails and no more code of the test case is executed. The following
|
|
example shows how the macro is used:
|
|
<screen>
|
|
|
|
int a, b;
|
|
a = 0, b = 1;
|
|
CPPUNIT_ASSERT(a != b);
|
|
a = 1;
|
|
CPPUNIT_ASSERT(a == b);
|
|
|
|
|
|
</screen>
|
|
The example shows, how two test steps are combined. One checks the
|
|
inequality of two integers, one the equality of them. If either one does
|
|
not work, the test case fails.
|
|
</para>
|
|
<para>
|
|
See the
|
|
<link linkend="test-source-example">Unit Test Source File Example</link>
|
|
for a demonstration of it's use.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="test-macro-fail">
|
|
<title>CPPUNIT_FAIL</title>
|
|
<para>
|
|
This is the macro used when the execution of a test case reaches a point it
|
|
should not. This usually happens, if exceptions are thrown or not thrown.
|
|
</para>
|
|
|
|
<para>
|
|
CPPUNIT_FAIL has a single argument which is the error message to be
|
|
displayed. The following example shows how the macro is used:
|
|
<screen>
|
|
|
|
int a = 1, b = 0;
|
|
try {
|
|
a = a / b;
|
|
CPPUNIT_FAIL("Expected exception missing!");
|
|
} catch (exception *e) {
|
|
delete e;
|
|
}
|
|
|
|
try {
|
|
a = a / a;
|
|
} catch (exception *e) {
|
|
delete e;
|
|
CPPUNIT_FAIL("Unexpected exception!");
|
|
}
|
|
|
|
|
|
</screen>
|
|
The example shows, how two test steps are combined. One checks the
|
|
occurance of an exception, the other one that no exception is thrown.
|
|
If either one does not work, the test case fails.
|
|
</para>
|
|
|
|
</sect2>
|
|
|
|
<sect2 id="test-macro-testsuite-start">
|
|
<title>CPPUNIT_TEST_SUITE</title>
|
|
<para>
|
|
This macro is used as the first thing in the declaration of the test fixture.
|
|
A single argument is the name of the class for the test fixture. It starts
|
|
the list of test cases in this fixture defined by the
|
|
<link linkend="test-macro-testcase">CPPUNIT_TEST</link> macro. The list must be
|
|
terminated using the <link
|
|
linkend="test-macro-testsuite-end">CPPUNIT_TEST_SUITE_END</link> macro.
|
|
</para>
|
|
<para>
|
|
See the
|
|
<link linkend="test-header-example">Unit Test Header File Example</link>
|
|
for a demonstration of it's use.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="test-macro-testsuite-end">
|
|
<title>CPPUNIT_TEST_SUITE_END</title>
|
|
<para>
|
|
This macro terminates the list of test cases in a test fixture. It has no
|
|
arguments.
|
|
</para>
|
|
<para>
|
|
See the
|
|
<link linkend="test-header-example">Unit Test Header File Example</link>
|
|
for a demonstration of it's use.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="test-macro-testcase">
|
|
<title>CPPUNIT_TEST</title>
|
|
<para>
|
|
This macro defines a new test case within a test fixture. As argument it
|
|
takes the name of the test case.
|
|
</para>
|
|
<para>
|
|
See the
|
|
<link linkend="test-header-example">Unit Test Header File Example</link>
|
|
for a demonstration of it's use.
|
|
</para>
|
|
</sect2>
|
|
|
|
<sect2 id="test-macro-registration">
|
|
<title>CPPUNIT_TEST_SUITE_REGISTRATION</title>
|
|
<para>
|
|
This macro registers a test fixture within a test suite. It takes the name
|
|
of the test fixture as argument.
|
|
</para>
|
|
<para>
|
|
See the
|
|
<link linkend="test-container-example">Unit Test Container Source File
|
|
Example</link>
|
|
for a demonstration of it's use.
|
|
</para>
|
|
</sect2>
|
|
|
|
</sect1>
|
|
</chapter>
|