How is a unit test class with nasty dependencies without a fake structure?

I work in an outdated C ++ code base, and I want to test some methods for the DependsOnUgly class, which has a dependency that is not easily broken into a large class ( Ugly ) with a lot of external dependencies on the file system, etc. I want to get at least some DependsOnUgly methods under the test, although I modify the existing code as little as possible. It is not possible to create a seam with a factory method, a method parameter, or a constructor parameter without a large number of code modifications; Ugly is a concrete class that directly depends on some abstract base class and has many methods, few or none of which are marked virtual , which would completely mock him would be very difficult. I don't have a fake structure, but I want to get DependsOnUgly in the test so that I can make changes. How can I break the external dependencies from Ugly to unit test methods on DependsOnUgly ?

+6
source share
1 answer

Use what I call the Mock Preprocessor - the layout is introduced through the preprocessor seam.

I first posted this concept in this question on Programmers.SE, and by the answers to this I judged that this is not a very well-known template, so I thought I should share it. It’s hard for me to believe that no one has done anything like this before, but since I could not find it documented, I thought I would share it with the community.

The following are conditional implementations of Ugly and NotAsUgly for example.

DependsOnUgly.hpp

 #ifndef _DEPENDS_ON_UGLY_HPP_ #define _DEPENDS_ON_UGLY_HPP_ #include <string> #include "Ugly.hpp" class DependsOnUgly { public: std::string getDescription() { return "Depends on " + Ugly().getName(); } }; #endif 

Ugly.hpp

 #ifndef _UGLY_HPP_ #define _UGLY_HPP_ struct Ugly { double a, b, ..., z; void extraneousFunction { ... } std::string getName() { return "Ugly"; } }; #endif 

There are two main options. The first is that only certain Ugly methods are called by Ugly , and you already want to simulate these methods. Second -

Technique 1: Replace all Ugly behavior used by DependsOnUgly

I call this method the Partial Mock preprocessor , because the layout only implements the necessary parts of the interface of the class that mocks it. Use include guards with the same name as the production class in the header file for the mock class to force the production class to never be defined, but rather a layout. Be sure to include the layout before DependsOnUgly.hpp .

(Note that my test file examples are not self-explanatory, it's just for the sake of simplicity and unit test framework agnostic. The focus is on the directives at the top of the file, not the actual testing method.)

test.cpp

 #include <iostream> #include "NotAsUgly.hpp" #include "DependsOnUgly.hpp" int main() { std::cout << DependsOnUgly().getDescription() << std::endl; } 

NotAsUgly.hpp

 #ifndef _UGLY_HPP_ // Same name as in Ugly.hpp---deliberately! #define _UGLY_HPP_ struct Ugly { // Once again, duplicate name is deliberate std::string getName() { return "not as ugly"; } // All that DependsOnUgly depends on }; #endif 

Technique 2: Replace some Ugly behavior used by DependsOnUgly

I call it Subclassed-in-Place Mock because in this case Ugly is a subclass and the necessary methods are overridden and the rest are still available for use - but the name of the subclass is still Ugly . The define directive is used to rename Ugly to BaseUgly ; it uses the undefined directive and Ugly layout subclasses of BaseUgly . Please note that this may require labeling something in Ugly as virtual, depending on the specific situation.

test.cpp

 #include <iostream> #define Ugly BaseUgly #include "Ugly.hpp" #undef Ugly #include "NotAsUgly.hpp" #include "DependsOnUgly.hpp" int main() { std::cout << DependsOnUgly().getDescription() << std::endl; } 

NotAsUgly.hpp

 #ifndef _UGLY_HPP_ // Same name as in Ugly.hpp---deliberately! #define _UGLY_HPP_ struct Ugly: public BaseUgly { // Once again, duplicate name is deliberate std::string getName() { return "not as ugly"; } }; #endif 

Please note that both of these methods are slightly unreliable and should be used with caution. They should be moved away, since most of the code base is under control and replaced by more standard means of breaking dependencies, if possible. Note that both can potentially be ineffective if the include directives of the legacy codebase are dirty enough. However, I have used them both successfully and for existing legacy systems, so I know that they can work.

+9
source

All Articles