Some times you happen to work on some code where it not easy to unit test a function.
It could be part of a large file where dependencies makes is impossible/hard to compile it in isolation. You may not want to use a sprout-method or in other ways change the target code to be testable, nonetheless you really want to unit test this one function with weird logic that your new shiny code depends on.
What if you could just have that function isolated so you could test it…
At times I have wondered where does such a tool exist? I have not found it. The Mocking frameworks I have seen they seem to prefer a nice clean interface or at least the ability to compiling a production version the file where the code you want under test is.
The goal is to have an easy and brutish way to extract a method, such that it may be tested in isolation.
The basic idea is to extract the source-code into a generated file, that can be used by the tests. Doing so in a manner such that any changes to the production function is picked up by the build system.
This relies on a primitive python-script and some CMake magic.
Imagine having the file ./production_code/gnarly_beast.cpp where you have an interesting function
#include "the_whole_world"
/// #ifdef THIS_AND_THAT
/// millions of omitted lines of code
int interesting_fun(CustomType bar) { return bar.complex_calculation() + 1; }
You are not allowed to change the production code but on build time you could generate a file from a ./test_code/beast.template
struct CustomType {
int injected_result;
int complex_calculation() { return injected_result; }
};
FUNSOLATE("int interesting_fun(CustomType bar)")
So in your tests ./test_code/test_beast.cpp you could write
#include <tamed/beast.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
CustomType bar;
bar.injected_result = 3;
if (interesting_fun(bar) == 4) {
return EXIT_SUCCESS;
} else {
return EXIT_FAILURE;
}
}
The test would be configured in your ./test_code/CMakeLists.txt with
set(beast "${CMAKE_CURRENT_SOURCE_DIR}/../production_code/gnarly_beast.cpp")
add_custom_command(
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/tamed/beast.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tamed/beast.h
COMMAND python3 ${CMAKE_CURRENT_SOURCE_DIR}/funsolator.py ${beast} ${CMAKE_CURRENT_SOURCE_DIR}/beast.template
DEPENDS ${beast} ${CMAKE_CURRENT_SOURCE_DIR}/beast.template
VERBATIM
)
Now any change to gnarly_beast.cpp
would generate a new tamed/beast.{cpp|h}
pair and you could
gain some confidence that the function behaves as expected - and continues to.
And changes to your beast.template
with the definition/inclusion of needed types would also result
in updates
- Sometimes you do not maintain the code you depend on. Think a third party library.
- The code maybe to gnarly to and you need something to start prying it open.
- You just want to see how to code works.
Often times, especially with C++/C code, you can instruct the compiler to compile a single file and just keep on adding stub implementations until the compilation succeeds. This can be tedious, almost impossible and a waste of time as you end up needing to maintain the stubs.
The Funsolator could be a tool to make this less tedious. With less friction it lowers the entry
barrier for adding tests & encourages more granular tests of the gnarly_beasts.cpp
out there.
This is just a toy example - to see if it was possible to do this.
It turns out it was not just pulling out the function, you also want a header to include, the correct incantations of CMake, ….
This generated output was added under the source dir tamed/beast.h
, as it will allow your IDE
to pick up the files.
Any nifty things to add could include:
- Better cmake functions to add the test
- Could it filter on extension, if its
.template
use thefunsolator
(which would probably need to define its “target file” / the beast - Turn into a CMake module to hide the functions and scripts
- Could it filter on extension, if its
- Try to create an example using
Makefiles
? - Use a real test framework for the example
~/Projects/funsolator$ cmake -G Ninja -B build .
~/Projects/funsolator$ ninja -C build
~/Projects/funsolator$ ninja -C build test