We encourage people writes unit testing shall follow F.I.R.S.T principle, but dependency gets in the way. Especially you make unit testing under legacy code, that’s mess. That’s the excuse we plan to ignore unit testing during coding.
For example, there is SUT code is game.c
:
1 2 3 4 5 6 7 |
|
function is_win()
relies on the return value of function dice_points()
which is defined in another file called dice.c
:
1 2 3 4 |
|
If we would like to test is_win()
, see the test case like this:
1 2 3 |
|
The problem is you don’t know the case will be pass or not, since the dependent function dice_points()
return value is volatile, this test case is unrepeatable. So we need define a test double called stub_dice_points()
to replace real implementation of dice_points()
, and return a value which is configurable, like this:
1 2 3 4 5 6 7 8 9 |
|
And during you execute is_win()
function, use stub_dice_points()
instead of dice_points()
, when you implement the unit testing like this:
1 2 3 4 |
|
But problem is how to replace the dice_points()
with stub_dice_points()
? There are some ways here:
1. Pre-compile macro
Macro #ifdef
, #else
and #endif
can be used during pre-compile phase, to select which source code statement can be compiled. So the SUT source code game.c
can be modified like this:
1 2 3 4 5 6 7 8 9 10 11 |
|
And during compile the source code for unit testing purpose, give the -DUNIT_TEST
compilation option, to enable the replacement. But this way requires you modify productive source code, and there will be a lot of macro like this, too ugly, and the source code readability will be getting worse.
2. Function pointer replacement
Another way is define a function pointer, in production code, the pointer is points a real depended function, and during unit testing, the function pointer will point the stub function, but it requires programmer change the production code like this, for example, the dice.h
change like this:
1
|
|
and the dice.c
change to this:
1 2 3 4 5 6 |
|
After that, in the test case, you can use the function pointer re-assignment like below (in cpputest framework):
1 2 3 4 5 |
|
The obvious disadvantage is production code changing is needed. You can image that, every function dependency shall be replaced by function pointer way, too ugly.
3. Linker links replaced object
Above two ways require to change the production code, is there any can avoid the production code change to complete the function replacement.
We assume the game.c
includes dice.h
, and dice.c
is to implement the function dice_ponits()
implementation. If we define another stub.c
to implement the function dice_points()
implementation in stub way. During compile the unit testing binary, don’t compile the dice.c
, just compile game.c
and stub.c
, and link them together to build a executable binary file.
But there will be another problem occurs: How many *.c
files under testing, there will be how many unit testing executable binary files. But, if the function is_win()
and dice_points()
in the same file, how to replace it? It will be difficult.
4. Dynamically replace
Is there any way to replace the dependency without do any production code change? The answer is yes, here we would like to introduce a tricky way to replace the dependency: During the function invoking, we let the invoking jump to our defined stub function.
For example, during isWin()
invoke dice_points()
, we change the memory stack information, leads the invoking to jump to stub_dice_points()
. The principle is first make the code page where dice_points()
located writable using mprotect()
in Linux or VirtualProect()
in Win32. Then overwrite a JMP
instructor which jump to stub_dice_poinots()
at the begin of dice_points()
binary code.
For example, there is a assistant source code assistant.c
which includes set_stup()
and reset_stub()
implementation. The two are used to the dependency replacement:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
|
And the assistant.c
is here:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
And then in the unit test file, you can use the assistant function for the dependency replacement:
1 2 3 4 5 6 7 |
|
5. Inherit from abstract class, replaced in dependency injection way
As we know, in OOP, there is interface concept, in C++, there is abstract class which used for interface purpose. If above the functions are defined as class way, there will be like this:
1 2 3 4 5 6 7 8 9 10 11 |
|
and the dice
class is like this:
1 2 3 4 5 6 |
|
If we would like to make the replacement, we have to define an abstract class called Player
like this:
1 2 3 4 |
|
And the class Dice
inherits from Player
like this:
1 2 3 4 5 6 |
|
And the Game
class shall make some change as well:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
The constructor function is used to dependency injection. In test purpose, we will inherit a new class called StubDice
from abstract class Player
, and use the StubDice
replace the real Dice
in test case:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
In the test case, the implementation as below:
1 2 3 4 5 6 |
|
Summary
We list the 5 ways of dependency breaking, it doesn’t mean that we suggest you use the ways in unit testing. During unit testing, if we find that it’s hard to make unit testing, we have to look back, check why it’s too hard to implement unit testing. Is it the design problem? Is it too many dependency, why? Is there any other way to implement same requirements? Refactor your source code often, make it better.
Treat the unit testing as design tool, helps you make better design; treat unit testing as safety tools, facilitates you make source code change.
-EOF-