如何为第三方遗留代码创建测试对象

时间:2009-01-07 16:56:47

标签: c++ unit-testing legacy

我有一个代码库,我实现的许多类派生自我公司其他部门提供的类。使用这些其他设备通常具有工作关系,就好像它们是第三方中间件供应商一样。

我正在尝试编写测试代码而不修改这些基类。但是,创建有意义的测试存在问题 由于缺少接口而导致的对象:

//ACommonClass.h
#include "globalthermonuclearwar.h" //which contains deep #include dependencies...
#include "tictactoe.h" //...and need to exist at compile time to get into test...

class Something //which may or may not inherit from another class similar to this...
{
public:
  virtual void fxn1(void);  //which often calls into many other classes, similar to this
  //...
  int data1;  //will be the only thing I can test against, but is often meaningless without fxn1 implemented
  //...
};

我通常从那里提取界面并开始工作,但由于这些是“第三方”,我无法提交这些更改。

目前,我已经创建了一个单独的文件,该文件包含在需要知道的基础上在第三方提供的基类头中定义的函数的虚假实现,如“使用传统代码”一书中所述。

我的计划是继续使用这些定义,并为我需要的每个第三方类提供替代测试实现:

//SomethingRequiredImplementations.cpp
#include "ACommonClass.h"
void CGlobalThermoNuclearWar::Simulate(void) {};  // fake this and all other required functions...
// fake implementations for otherwise undefined functions in globalthermonuclearwar.h's #include files...
void Something::fxn1(void) { data1 = blah(); } //test specific functionality.

但是在我开始这样做之前,我想知道是否有人尝试在类似于我的代码库上提供实际对象,这将允许创建新的测试特定类来代替实际的第三方类。

请注意,所有相关的代码库都是用C ++编写的。

5 个答案:

答案 0 :(得分:1)

Mock objects适合此类任务。它们允许您模拟其他组件的存在而无需它们存在。您只需在测试中定义预期的输入和输出。

Google有一个很好的C ++模拟框架。

答案 1 :(得分:1)

此刻我遇到了一个非常类似的问题。我不想添加一堆仅用于测试目的的接口,因此我不能使用任何现有的模拟对象库。为了解决这个问题,我做了同样的事情,用假实现创建一个不同的文件,并让我的测试链接假行为,生产代码链接真实的行为。

我希望我现在可以做的是,采用另一个模拟框架的内部,并在我的假对象中使用它。看起来有点像这样:

Production.h

class ConcreteProductionClass { // regular everyday class
protected:
    ConcreteProductionClass(); // I've found the 0 arg constructor useful
public:
    void regularFunction(); // regular function that I want to mock
}

Mock.h

class MockProductionClass 
    : public ConcreteProductionClass
    , public ClassThatLetsMeSetExpectations 
{
    friend class ConcreteProductionClass;
    MockTypes membersNeededToSetExpectations;
public:
    MockClass() : ConcreteProductionClass() {}
}

ConcreteProductionClass::regularFunction() {
    membersNeededToSetExpectations.PassOrFailTheTest();
}

ProductionCode.cpp

void doSomething(ConcreteProductionClass c) {
    c.regularFunction();
}

Test.cpp的

TEST(myTest) {
    MockProductionClass m;
    m.SetExpectationsAndReturnValues();
    doSomething(m);
    ASSERT(m.verify());
}

所有这一切中最痛苦的部分是其他模拟框架与此非常接近,但不完全这样做,并且宏如此复杂以至于调整它们并非易事。我在业余时间开始研究这个问题,但它并没有很快地进行。即使我让我的方法以我想要的方式工作,并且期望设置代码到位,这个方法仍然有一些缺点,其中之一就是如果你必须链接反对你的构建命令可能会有点长很多.o文件而不是一个.a文件,但这是可管理的。由于我们没有链接它,因此也无法实现默认实现。无论如何,我知道这不回答这个问题,或者甚至告诉你任何你还不知道的事情,但它表明了C ++社区能够模拟没有纯虚拟接口的类的接近程度。

答案 2 :(得分:0)

您可能需要考虑mocking而不是伪造作为潜在的解决方案。在某些情况下,如果原始类不是,则可能需要编写可模拟的包装类。我用C#/ .Net中的框架类完成了这个,但不是C ++,所以YMMV。

答案 3 :(得分:0)

如果我有一个我需要在测试中需要的课程来自我不能(或不想)在测试中运行的东西,我会:

  1. 制作一个新的逻辑专用课程。
  2. 将code-i-wanna-test移动到逻辑类。
  3. 使用接口与真实类进行对话,以便与基类进行交互和/或我不能或不会将其放入逻辑中。
  4. 使用相同的界面定义测试类。这个测试类只能有模拟真实类的noops或花式代码。
  5. 如果我有一个我只需要在测试中使用的类,但使用真正的类是一个问题(依赖或不需要的行为):

    1. 我将定义一个新界面,它看起来像我需要调用的所有公共方法。
    2. 我将创建一个支持该界面进行测试的对象的模拟版本。
    3. 我将创建另一个用该类的“真实”版本构建的类。它还支持该接口。所有接口调用转发到真实对象方法。
    4. 我只对我实际调用的方法执行此操作 - 而不是所有公共方法。我会在写更多测试时添加这些类。
    5. 例如,我像这样包装MFC的GDI类来测试Windows GDI绘图代码。模板可以使其中的一些变得更容易 - 但由于各种技术原因(Windows DLL类导出的内容......),我们常常不会这样做。

      我确信这一切都在Feather的使用遗留代码一书中 - 而我所描述的内容却有实际用语。只是不要让我把书从书架上拉下来......

答案 4 :(得分:0)

您在问题中未指出的一件事是您的类派生自其他部门的基类的原因。这种关系真的是一个IS-A关系吗?

除非您的类需要由框架使用,否则您可以考虑支持委托而不是继承。然后,您可以使用依赖注入为您的类提供单元测试中的类的模拟。

否则,我们的想法是编写一个脚本来从他们提供的标题中提取和创建您需要的接口,并将其集成到编译过程中,以便您的单元测试可以检入。