繁体   English   中英

如何在没有模拟框架的情况下对具有令人讨厌的依赖性的类进行单元测试?

[英]How do I unit test a class with nasty dependencies without a mock framework?

我正在使用遗留的C ++代码库,我想测试一个类DependsOnUgly上的一些方法,它们具有依赖性,这种依赖性在大型类( Ugly )上很容易被破坏,在文件系统上有很多外部依赖,等等。我想要至少得到DependsOnUgly一些方法,同时尽可能少地修改现有代码。 没有很多代码修改,无法通过工厂方法,方法参数或构造函数参数创建接缝; Ugly是一个直接依赖的具体类,没有任何类型的抽象基类,并且有很多方法,很少或没有一个标记为virtual ,完全嘲笑它会非常危险。 我没有可用的模拟框架,但我想让DependsOnUgly接受测试,以便我可以进行更改。 如何打破Ugly的外部依赖关系来对DependsOnUgly上的方法进行单元测试?

使用我所谓的预处理器模拟 - 通过预处理器接缝注入的模拟。

我首先在这个关于Programmers.SE的问题中发布了这个概念,并且根据我的答案我判断这不是一个众所周知的模式,所以我认为我应该分享它。 我发现很难相信之前没有人做过这样的事情,但因为我找不到记录,我想我会与社区分享。

以下是UglyNotAsUgly名义实现。

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所有行为

我将此技术称为预处理器部分模拟,因为模拟仅实现被模拟的类的接口的必要部分。 在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使用的Ugly行为

我称之为Subclassed-in-Place Mock,因为在这种情况下, Ugly是子类,并且必要的方法被覆盖而其他方法仍可供使用 - 但是子类的名称仍然是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伪指令足够混乱,它们可能都会失效。 但是, 我已经成功地将它们用于实际的遗留系统 ,所以我知道它们可以工作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM