Creating testable code
I have a file - in a large legacy codebase - containing methods that access databases. No classes are used, just a header file with the method declar开发者_Go百科ations, and the source file with the implementation.
I want to override these methods to eliminate the DB access during unit testing.
I thought of the following options:
- Make file into class and override methods. The main minus here is that it results in alot of changes throughout the codebase. Not ideal, though it does improve the code...
- Wrap the whole source file with an
#ifdef PRODUCTION_CODE
and create a new source file containing the stub and wrap it with the opposite, i.e. make the whole thing compilation dependent. Problem here is that in a build system that performs regression tests I would have to compile twice, once in order to create the apps and do regression tests, and an additional time to create the unit test executables.
Any recommended ways of doing this?
How about taking the existing functions, move the code inside into a new class and call the new methods from the existing functions and then override this class during tests?
Like so:
static DBAccessClass dac = new DBAccessClass ();
void origFunction() { dac.origFunction(); }
And in the test:
dac = new DBAccessMockup();
You can try to override at the linker level. Have two different .cpp files that implement the functions in the header that describes the DB interface - one calling the real DB, one calling a fake interface. When you link for unit test, replace one with the other (Target Specific Variables in GNU make may help).
You may also want to look at Michael Feathers' book Working Effectively with Legacy Code. Not only does he discuss exactly these types of problems, but the book includes numerous examples in C++ (in addition to Java, C, and C#). Feathers is also the original creator of CppUnit.
- Make file into class and override methods. [...]
- Wrap the whole source file with an #ifdef [...]
If possible, go with 1, as you will have a centralized way of referring to the database code (a class pointer instead of X functions). That means modularity, ease in replacing the implementation (with stubs or with another DB back-end) and more encapsulated code.
If you go with 2, consider replacing the implementation of the functions. That is, inside the original functions, engage the testing code (based on an if
).
Your tested code is completely agnostic if running in a test environment or not, the performance loss is negligible (if(booleanFlagHere)
is a negligible cost in most cases) and you don't have to modify the tested code at all).
精彩评论