To emulate the behaviour of an object in your production code you need more than just single values that meet your expectations. Usually you need a collection of the different expectation types and some code to generate a deterministic and predictable behaviour to test a limited area.
The following methods all require a separation of the code under test and the code using it. But most of the times you would split the two parts anyway to increase maintainability.
class MyInterface { public: virtual void theMethod(unsigned param) = 0; }; class MyProductionClass : public MyInterface { public: virtual void theMethod(unsigned param) { ... } }; class MyMockObject : public MyInterface { ExpectationList<unsigned> methParams; public: MyMockObject(String name, Verifiable *v) : MockObject(name, v) , methParams("method parameters", this) { methParams.addExpected(1); methParams.addExpected(2); } virtual void theMethod(unsigned param) { methParams.addActual(param); ... } }; MyMockObject mo (MOCKPP_PCHAR("mockObject"), 0); myMethodUnderTest(&mo); mo.verify(); MyProductionClass pro; myMethodUnderTest(&pro);
Define an interface class which contains all the methods that describe some real world behaviour. This interface class is also used to implement the real world class. The intention is to pass a real world object in the running application or a mock object while executing tests. | |
Derive your real world production code from the abstract base class to implement our interface as you need it. | |
Derive the mock object and implement it with a minimum of the functionality required in an absolutely deterministic way. In the example we set up a list of expected parameters to the method and have it verified by the mock object automatically. | |
Instantiate the mock object and pass it to the method you want to test
while you are in the test routines. After completing the test code call
| |
To run the production code instantiate the real object and pass it to the method. |
Since most of the tasks concerning mock objects occur frequently and in a similar manner there are some classes which support automated behaviour with different approaches. Depending on your personal taste or special problems you may choose the one or the other.
VisitableMockObject
. It was named like this
because you set up part of its behaviour by recording an execution path for the tests,
thus by "visiting" the object. This approach is rather strict because the invocation order
and parameter values must match exactly. Unfortunately it is almost impossible to add
new capabilities.
ChainableMockObject
.
The name stems from the fact that you define the
behaviour with a "chain" of invocations of a helper object. Each chain link sets a constraint, a return
value or some other aspect. The big benefit from this approach is the extensibility
concerning the behaviour by simply feeding it new objects which reflect your needs. Therefor it is
far more flexible in execution: everything depends on user defined objects outside the
mock object. A large set of such classes is already included.More detailed explanations follow in later chapters.
The basic principle for the use of these classes is the same for both methods:
There are two standard ways to set up the behaviour of advanced mock objects.
The traditional way using macros which implement everything behind the scenes and an additional helper object which controls these helper objects.
But caution: if you take the wrong macro or maybe just have a small typo you will get loads of error messages all pointing to the same line. I strongly recommend to take little steps when setting up such objects: add one single method and compile. To get started I recommend to use the "recipes" below.
If you want to have an overview what is created behind the scenes, look at
chainable-template.h
and
visitable-template.h
which contain example classes with some methods.
They were preprocessed, automatically indented and are quite readable.
This knowledge might become useful when you need to find out more about the referenced objects
and their internal helper variables when runtime errors occur.
The newer and recommended approach with the help of mock methods. The methods are templates which wrap the method under test. Since they are fully accessible in contrast to an obscure macro they are easier to use and better to debug.
For both the Visitable and the Chainable types there are sets of templates for mock methods according to the number of parameters the method under test takes. But most of the current compilers support a shorthand version which selects the needed template automatically.
So the following lines are equivalent:
VisitableMockMethod0<void> mmv0(MOCKPP_PCHAR("mm0"), 0); VisitableMockMethod1<void, int> mmv1(MOCKPP_PCHAR("mm1"), 0); VisitableMockMethod2<void, int, int> mmv2(MOCKPP_PCHAR("mm2"), 0); VisitableMockMethod<void> mmvd0(MOCKPP_PCHAR("mm0"), 0); VisitableMockMethod<void, int> mmvd1(MOCKPP_PCHAR("mm1"), 0); VisitableMockMethod<void, int, int> mmvd2(MOCKPP_PCHAR("mm2"), 0);
There is another point to keep in mind when choosing either the macros or the
templates. There are only macros available for up to five parameters. Extending this
number probably results
in a lot of coding and debugging. The templates on the other hand are generated by several
scripts. The standard distribution supports up to 6 parameters. If you need more you only
have to invoke gen_files_N.sh
in the top level directory of the
source distribution. Supply the maximal number of parameters you need and all the header
files are generated.
There are sets of macros for const and non-const methods, the visitable and the chainable way and each combination of them with up to five parameters. I assume you will not want to have code with more than 5 parameters. But if you really think you need more, take the macros with 5 parameters and try to extend it. It is really straightforward and please don't forget to extend the test files. You may then of course send me the result for inclusion and further maintainance.
Due to the nature of macros there are some limitations concerning the automatically generated methods and the according internal helper variables.
It may become necessary to implement operator<<()
as
debug output is created this way. One approach is to provide a meaningful operator method.
If this
does not make much sense you may use a default template instead which simply outputs
the raw classname. To avoid pontential conflicts this template must be
enabled by setting the macro
MOCKPP_ENABLE_DEFAULT_FORMATTER before including the first mockpp header
file.
It is also possible to re-use an existing
operator<<(std::ostream &, const T &val)
.
In this case you supply depending on the string type one of the macros
MOCKPP_OWSTREAMABLE
or MOCKPP_OSTREAMABLE. This implements a simple string translator function.
The total number of macros is quite huge but there is a scheme behind it:
The macro for the initialiser list in the constructor is built like this:
To determine the macro for the method write the following:
The macro for the helper object to set up the behaviour needs these steps:
Here are some examples:
To see examples for all the macros have a look at the unit test files in the
tests
directory. Especially interesting are the files
ChainableMockObject_*
and
VisitableMocObject_*
.
If the available mock objects and methods don't give you fine enough control over
your mock object there is
only one solution: code and implement the desired behaviour manually. Look at
chainable-template.h
and
visitable-template.h
to get an idea of the
standard implementation.
But I assume this will not happen too often because mock objects should only cover a very limited aspect, only what is currently really needed for a special test scope.
After the last invocation to a mock object has occurred you should always invoke
verify()
on the mock object to verify all pending expectations.
Maybe there are some unused return objects or outstanding calls. If you use CppUnit as
test framework you should use VerifyingTestCase
from mockpp
and register your mock objects on creation with it. In contrast to the standard
TestCase
from the CppUnit package it invokes verify()
on all mock objects after a test case passed. This way it is impossible to forget it.
Unfortunately this class is partially based on an error in reasoning and became more complicated as originally thought. So it is less useful as expected. Decide yourself if it is of any advantage in your application.