假/模拟非虚拟C ++方法

时间:2010-11-25 16:19:55

标签: c++ testing mocking

众所周知,在C ++中,模拟/伪造非虚拟测试方法很难。例如,cookbook of googlemock有两个建议 - 两者都意味着以某种方式修改原始源代码(模板化和重写为接口)。

对于C ++代码来说,这似乎是一个非常糟糕的问题。如果你不能修改需要伪造/嘲笑的原始代码,怎么办才能做得最好?复制整个代码/类(用它整个基类层次结构??)

8 个答案:

答案 0 :(得分:11)

我们有时使用的一种方法是将原始.cpp文件拆分为至少两部分。

然后测试设备可以提供自己的实现;有效地使用链接器为我们做了肮脏的工作。

在某些圈子中称为“Link Seam”。

答案 1 :(得分:7)

必须使用您使用的任何测试技术编写代码以使其可测试。如果你想使用模拟测试,这意味着某种形式的依赖注入。

不依赖于模板参数的非虚拟调用与Java [*]中的finalstatic方法存在同样的问题 - 被测代码明确表示,“我想调用这个代码,而不是一些在某种程度上依赖于参数的未知代码“。作为测试人员,您希望它从正常调用的内容中调用不同的代码。如果您无法更改测试中的代码,那么您(测试人员)将失去该参数。您还可以询问如何在不更改测试代码的情况下引入10行函数的第4行的测试版本。

如果要模拟的类与被测试的类位于不同的TU中,则可以编写与原始类相同的模拟并将其链接起来。无论你是否能以正常的方式使用你的模拟框架生成模拟,我都不太确定。

如果你愿意,我认为这是一个“非常糟糕的C ++问题”,它可以编写难以测试的代码。它与许多其他语言共享这个“问题”......

[*]我的Java知识非常低。在Java中可能有一些巧妙的方法来模拟这样的方法,这些方法不适用于C ++。如果是这样,请忽略它们以便看到类比; - )

答案 2 :(得分:7)

我关注了Link Seamsdg's answer链接。在那里我读到了不同类型的接缝,但我对预处理接缝印象最深刻。这让我想到了进一步利用预处理器。事实证明,可以模拟任何外部依赖,而无需实际更改调用代码

为此,您必须使用替换依赖项定义编译调用源文件。 这是一个如何做的例子。

dependency.h

#ifndef DEPENDENCY_H
#define DEPENDENCY_H

class Dependency
{
public:
    //...
    int foo();
    //...
};

#endif // DEPENDENCY_H

caller.cpp

#include "dependency.h"

int bar(Dependency& dependency)
{
    return dependency.foo() * 2;
}

TEST.CPP

#include <assert.h>

// block original definition
#define DEPENDENCY_H

// substitute definition
class Dependency
{
public:
    int foo() { return 21; }
};

// include code under test
#include "caller.cpp"

// the test
void test_bar()
{
    Dependency mockDependency;

    int r = bar(mockDependency);

    assert(r == 42);
}

请注意,mock不需要实现完整的Dependency,只需要最小(由caller.cpp使用),这样测试就可以编译和执行。 这样,您可以在不更改生产代码的情况下模拟非虚拟,静态,全局函数或几乎任何依赖项。 我喜欢这种方法的另一个原因是与测试相关的所有内容都在一个地方。你不必在这里和那里调整编译器和链接器配置。

我已经成功地将这项技术应用于具有大量脂肪依赖性的真实世界项目中。 我在Include mock中更详细地描述了它。

答案 3 :(得分:1)

@zaharpopov您可以使用Typemock IsolatorPP创建非虚拟类和方法的模拟,而无需更改代码(或遗留代码)。 例如,如果您有一个名为MyClass的非虚拟类:

class MyClass
{
 public:
   int GetResult() { return -1; }
}

你可以使用typemock来模拟它:

MyClass* fakeMyClass = FAKE<MyClass>();
WHEN_CALLED(fakeMyClass->GetResult()).Return(10);

顺便说一下,您要测试的类或方法也可以是私有,因为typemock也可以模拟它们,例如:

class MyClass
{
private:
   int PrivateMethod() { return -1; }
}


MyClass* myClass =  new MyClass();

PRIVATE_WHEN_CALLED(myClass, PrivateMethod).Return(1);

了解更多信息,请访问here

答案 4 :(得分:1)

我认为现在不可能使用标准C ++(但是希望很快就会有一个强大的编译时反射到C ++ ......)。但是,有很多选择。

您可以查看Injector++。它现在只是Windows,但计划增加对Linux和Linux的支持。 Mac中。

另一个选项是CppFreeMock,它似乎与GCC合作,但最近没有活动。

HippoMocks也提供这样的能力,但仅限于免费功能。它不支持类成员函数。

我不完全确定,但似乎以上所有都是通过在运行时覆盖目标函数来实现这一点,以便它跳转到伪造函数。

C-Mock,它是Google Mock的扩展,允许您通过重新定义非虚拟函数来模拟非虚函数,并依赖原始函数在动态库中的事实。它仅限于GNU / Linux平台。

最后,您也可以尝试PowerFake(我为作者而言)here

它不是一个模拟框架(当前),它提供了用测试替换生产函数的可能性。我希望能够将它集成到一个或多个模拟框架中;如果没有,它就会变成一个。

它还会在链接期间覆盖原始函数(因此,如果在定义函数的同一个转换单元中调用函数,它将无法工作),但使用与C-Mock不同的技巧GNU ld&#39; --wrap选项。它还需要对构建系统进行一些更改以进行测试,但不会以任何方式影响主代码(除非您被迫将函数放在单独的.cpp文件中);但是提供了对将其轻松集成到CMake项目中的支持​​。

但是,它目前仅限于GCC / GNU ld(也适用于MinGW)。

答案 5 :(得分:0)

您非常具体地说“如果您无法修改原始代码”,您在问题中提到的技术(以及所有其他当前“答案”)都会这样做。

在不更改源的情况下,您通常仍可以(对于常见的OS /工具)预加载一个对象,该对象定义了您想要拦截的函数的自己版本。他们甚至可以在之后调用原始函数。我在(我的)问题Good Linux TCP/IP monitoring tools that don't need root access?中提供了一个这样做的例子。

答案 6 :(得分:-2)

我曾经为我需要模拟的部分创建一个界面。然后我简单地创建了一个派生自该接口的存根类,并将此实例传递给我的测试类。是的,这是一项艰苦的工作,但我发现在某些情况下它值得。

哦,通过界面我的意思是struct只有 纯虚拟方法。没别了!

答案 7 :(得分:-2)

这比你想象的容易。只需将构造的对象传递给您正在测试的类的构造函数。在类存储中对该对象的引用。然后很容易使用模拟类。

编辑:

传递给构造函数的对象需要一个接口,该类只存储对接口的引用。


struct Abase
{
  virtual ~Abase(){}
  virtual void foo() = 0;
};

struct Aimp : public Abase
{
  virtual ~Aimp(){}
  virtual void foo(){/*do stuff*/}
};

struct B
{
  B( Aimp &objA ) : obja( objA )
  {
  }

  void boo()
  { 
    objA.foo();
  }

  Aimp &obja;
};


int main()
{
//...
Aimp realObjA;
B objB( realObjA );
// ...
}

在测试中,您可以轻松传递模拟对象。