【问题标题】:Mocking non-virtual methods in C++ without editing production code?在不编辑生产代码的情况下在 C++ 中模拟非虚拟方法?
【发布时间】:2011-01-21 07:49:14
【问题描述】:

我是一名相当新的软件开发人员,目前正在为多年前开始的现有 C++ 项目添加单元测试。由于非技术原因,我不允许修改任何现有代码。我所有模块的基类都有一堆设置/获取数据和与其他模块通信的方法。

由于我只想对每个单独的模块进行单元测试,因此我希望能够为我的所有模块间通信方法使用固定值。 IE。对于检查另一个模块是否处于活动状态的 Ping() 方法,我希望它根据我正在做的测试类型返回 true 或 false。我一直在研究 Google Test 和 Google Mock,它确实支持模拟非虚拟方法。然而,所描述的方法 (http://code.google.com/p/googlemock/wiki/CookBook#Mocking_Nonvirtual_Methods) 要求我将原始方法“模板化”以获取真实或模拟对象。由于前面提到的要求,我不能在基类中模板化我的方法,所以我需要一些其他方法来模拟这些虚拟方法

基本上,我要模拟的方法在某个基类中,我要对其进行单元测试和创建模拟的模块是该基类的派生类。在我的基本 Module 类和我要测试的模块之间有中间模块。

如果有任何建议,我将不胜感激!

谢谢,

JW

编辑:更具体的例子

我的基类是rootModule,我要测试的模块是leafModule。有一个继承自rootModule的中间模块,leafModule继承自这个中间模块。

在我的leafModule 中,我想测试doStuff() 方法,该方法调用rootModule 类中定义的非虚拟GetStatus(moduleName)。我需要以某种方式使 GetStatus() 返回一个选定的罐头值。模拟对我来说是新事物,所以使用模拟对象是否是正确的方法?

【问题讨论】:

  • 你能在本地修改代码只为了你的测试吗?您不必签入更改。
  • 你需要使用单元测试吗?还有其他(自动)回归测试方法。单元测试是一种工具,而不是一种宗教——如果它不适合,就不要使用它。
  • 理想情况下,我的团队希望使用单元测试,因为我们的模块树相当大,我们真的希望将模块间功能的测试与模块本地方法的测试分离。不幸的是,我们之前的设计决策使单元测试成为了一个挑战。您指的是哪些其他回归测试方法?我想,使用其他测试方法,我们仍然需要某种方法来设置非虚拟方法或返回固定值,这意味着我们要么找到解决方法,要么保留另一个源代码树。
  • +1 用于 Google Mocks 食谱链接。我确实有这个问题,但是“模板化”方法已经成为我设计的一部分,所以听起来它对我来说可能是理想的。

标签: c++ unit-testing mocking googlemock


【解决方案1】:

有一些不同的方法可以替换非虚拟函数。一种是重新声明它们并为您想要测试的每组不同的非虚拟函数编译一个新的测试可执行文件。这很难扩展。

第二种选择是使它们虚拟化以进行测试。大多数编译器都允许您在命令行上定义某些内容,因此请使用 -DTEST_VIRTUAL=virtual 或 -DTEST_VIRTUAL 编译您的代码,以根据代码是否在测试中将它们设为虚拟或正常。

第三个可用的选项是使用一个模拟框架,让您模拟非虚拟函数。我是 HippoMocks 的作者(关于中立性等的免责声明),我们最近添加了在 X86 平台上模拟纯 C 函数的能力。这可以通过一些工作扩展到非虚拟成员函数,这就是您正在寻找的。请记住,如果您的编译器可以同时看到函数的使用和定义,那么它可能会内联它并且模拟可能会失败。这尤其适用于在标头中定义的函数。

如果常规的 C 函数模拟对您来说已经足够了,您可以照原样使用它。

【讨论】:

  • 我刚刚遇到了这个答案。我一直打算使用 HippoMocks 一段时间(第一次使用模拟框架),但我倾向于使用模板进行编译时依赖注入,我不确定 HippoMocks 是否可以管理这个。您能否进一步解释一下模拟非虚拟成员函数需要什么?为什么内联会导致模拟失败?
  • 非虚拟成员函数可以由编译器在编译单元期间内联,或者由链接器在链接时 (LTO) 内联。这两者都意味着函数的“地址”不再是明确定义的,因此用其他东西替换它也是不可能的,因为您找不到那些其他“新”位置。对于虚拟成员函数,您可以保证不会发生这种情况(因为它们是虚拟的,所以不可能内联)。
  • 通过替换函数的前 N ​​个字节来模拟非虚拟函数——实际上是在运行时破解代码——跳转到模拟函数。这仅在函数只有一个位置要编辑时才有效;你不能有一个指向多个位置的函数指针。答案分为两条评论,因为它们有长度限制。
  • 我使用preprocessor directives 这样做。
【解决方案2】:

我会编写一个 Perl/Ruby/Python 脚本来读取原始源代码树并在不同的目录中写出一个模拟的源代码树。您不必完全解析 C++ 来替换函数定义。

【讨论】:

  • 这可能行得通,但是 IMO 只拥有 2 个源树并在其中一个中将基类方法更改为虚拟会更简单。负责源代码控制系统的人员会受到一些不利影响。开发人员还必须调整自己以编写 2 个工作修订版的代码。我正在寻找一种解决方法,它可以让我将我的单元测试集成到一个可以一次性构建(和执行)的源代码树中。我不确定这是否可能。
  • @wk1989:开发人员永远不会使用脚本思想在两个源代码树上工作。这就是重点。所有代码编辑都将在真实的代码树上完成。测试将在脚本生成的测试树上运行。
【解决方案3】:

一种方法是指定不同的测试来源。假设您的生产目标使用rootModule.hrootModule.cpp。为您的测试目标使用不同的来源。您可以通过更改包含路径来指定不同的标头,以便#include "rootModule.h" 实际加载unittest/rootModule.h。然后将 rootModule 模拟成你想要的内容。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-07
    • 1970-01-01
    • 1970-01-01
    • 2013-11-22
    • 2011-09-25
    • 1970-01-01
    • 2012-07-29
    • 1970-01-01
    相关资源
    最近更新 更多