解决方法
我在this question年首次在Programmers.SE上发布了这个概念,并且通过答案我判断这不是一个众所周知的模式,所以我认为我应该分享它.我发现很难相信之前没有人做过这样的事情,但因为我找不到记录,我想我会与社区分享.
以下是Ugly和NotAsUgly的名义实现.
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
有两种基本的变化.第一个是DependsOnUgly只调用Ugly的某些方法,你已经想要模拟那些方法.第二是
技巧1:替换DependsOnUgly使用的丑陋的所有行为
我将此技术称为预处理器部分模拟,因为模拟仅实现被模拟的类的接口的必要部分.在mock类的头文件中使用包含与生产类相同名称的保护,以使生产类永远不会被定义,而是模拟.一定要在DependsOnUgly.hpp之前包含模拟.
(请注意,我的测试文件示例不是自我验证的;这只是为了简单起见并且与单元测试框架无关.重点是文件顶部的指令,而不是实际的测试方法本身.)
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
技巧2:替换DependsOnUgly使用的丑陋行为
我称之为Subclassed-in-Place Mock,因为在这种情况下,Ugly是子类,并且必要的方法被覆盖而其他方法仍可供使用 – 但是子类的名称仍然是丑陋的. define指令用于将Ugly重命名为BaseUgly;然后使用undefine指令,并使用模拟Ugly子类BaseUgly.请注意,根据具体情况,这可能需要将Ugly中的某些内容标记为虚拟内容.
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
请注意,这两种方法都有点不稳定,应谨慎使用.随着更多的代码库正在测试中,它们应该被移开,并且如果可能的话,用更多标准的破坏依赖性的方法替换它们.请注意,如果遗留代码库的include伪指令足够混乱,它们可能都会失效.但是,我已经成功地将它们用于实际的遗留系统,所以我知道它们可以工作.