如何在没有模拟框架的情况下对具有严重依赖关系的类进行单元测试?
How do I unit test a class with nasty dependencies without a mock framework?
我在一个遗留的c++代码库中工作,我想测试一个类的一些方法,DependsOnUgly
,它有一个依赖关系,不容易在一个大类(Ugly
)上破坏,在文件系统上有很多外部依赖关系,等等。我想至少得到一些DependsOnUgly
的方法在测试中,同时尽可能少的修改现有的代码。在没有大量代码修改的情况下,没有办法通过工厂方法、方法参数或构造函数参数来创建seam;Ugly
是一个直接依赖的具体类,没有任何抽象基类,有大量的方法,很少或没有标记为virtual
,完全模仿它将是非常辛苦的。我没有可用的模拟框架,但我想让DependsOnUgly
在测试中,这样我就可以进行更改。如何打破Ugly
的外部依赖来单元测试DependsOnUgly
上的方法?
使用我所说的预处理器模拟—通过预处理器接缝注入的模拟。
我第一次在程序员的这个问题上发表了这个概念。根据他们的回答,我判断这不是一个众所周知的模式,所以我想我应该和大家分享一下。我很难相信以前没有人做过这样的事情,但是因为我找不到它的文档,所以我想我应该与社区分享。
这里是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
使用的Ugly
的所有行为
我将此技术称为预处理器部分模拟,因为模拟只实现被模拟类的接口的必要部分。在模拟类的头文件中使用与生产类同名的include守卫,这样就不会定义生产类,而是定义模拟类。请确保在DependsOnUgly.hpp
之前包含mock。
(注意,我的测试文件示例不是自我验证的;这只是为了简单起见,并且与单元测试框架无关。重点放在文件顶部的指令上,而不是实际的测试方法本身。
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
使用的Ugly
的一些行为
我将其称为子类就地模拟,因为在这种情况下,Ugly
被子类化,必要的方法被覆盖,而其他方法仍然可用,但子类的名称仍然是Ugly
。使用define指令将Ugly
重命名为BaseUgly
;然后使用一个未定义指令,模拟Ugly
子类BaseUgly
。请注意,这可能需要根据具体情况将Ugly
中的某些内容标记为virtual。
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指令足够混乱,它们都可能变得无效。然而,我已经在实际的遗留系统中成功地使用了它们,所以我知道它们可以工作。
- C++GTKMM gui循环依赖关系
- 如何在头文件中声明类模板(由于循环依赖关系)
- 对在不同二进制文件中创建的对象文件的依赖关系
- 使用Bazel构建具有不同编译器/链接器选项的C/C++依赖关系
- OpenVINO - 推理库插件 libMKLDNNPlugin.so 无法解析依赖关系
- 模拟测试中类的依赖关系
- C++模板方法中的循环依赖关系
- 解析正交模块的依赖关系
- 如何在 Mac OS 上安装 boost-mpi 及其对 clang 的依赖关系?
- Wt::D bo 中的循环依赖关系
- 在包含窗口标头时难以解决循环依赖关系问题
- 当依赖关系和依赖关系都是多态时,在哪个继承级别存储依赖关系指针?
- 解决循环依赖关系 c++ 的想法
- C++循环依赖关系,未声明的标识符
- C++ 中的循环依赖关系问题
- 为什么包含需要进一步的依赖关系?
- 使用 cmake 获取外部依赖关系
- CMake 外部和内部静态库的循环依赖关系
- 在没有Xcode的macOS中开发具有依赖关系的应用程序
- "std::shared_ptr"循环依赖关系是如何导致问题的