C ++中的模拟对象是否总是需要虚拟方法或模板?

时间:2011-03-24 21:57:52

标签: c++ unit-testing mocking googletest

假设我有课程

class Inner {
  public:
    void doSomething();
};

class Outer {
  public:
    Outer(Inner *inner);  // Dependency injection.

    void callInner();
};

正确的单位测试说我应该对Inner进行测试。然后,我应该对Outer进行测试,该测试不使用真正的Inner而是使用MockInner,这样我就可以对Outer添加的功能进行单元测试而不是完整堆栈Outer / Inner

为此,Googletest似乎建议将Inner转换为纯粹的抽象类(接口),如下所示:

// Introduced merely for the sake of unit-testing.
struct InnerInterface {
  void doSomething() = 0;
};

// Used in production.
class Inner : public InnerInterface {
  public:
    /* override */ void doSomething();
};

// Used in unit-tests.
class MockInner : public InnerInterface {
  public:
    /* override */ void doSomething();
};

class Outer {
  public:
    Outer(Inner *inner);  // Dependency injection.

    void callInner();
};

所以,在生产代码中,我会使用Outer(new Inner);在测试中,Outer(new MockInner)

行。理论上似乎很好,但是当我开始在整个代码中使用这个想法时,我发现自己为每个怪异的类创建了一个纯粹的抽象类。即使您可以忽略由于不必要的虚拟调度导致的轻微运行时性能降级,也会进行大量的锅炉版打字。

另一种方法是使用模板,如下所示:

class Inner {
  public:
    void doSomething();
};

class MockInner {
  public:
    void doSomething();
};

template<class I>
class Outer {
  public:
    Outer(I *inner);

    void callInner();
};

// In production, use
Outer<Inner> obj;

// In test, use
Outer<MockInner> test_obj;

这避免了电镀锅炉和不必要的虚拟调度;但现在我的整个代码库都处于怪异的头文件中,这使得无法隐藏源代码实现(更不用说处理令人沮丧的模板编译错误和长构建时间)。

这两种方法,虚拟和模板,是进行正确单元测试的唯一方法吗?有没有更好的方法进行适当的单元测试?

通过适当的单元测试,我的意思是每个单元测试只测试该单元引入的功能,但不测试单元的依赖性

2 个答案:

答案 0 :(得分:6)

我不认为你必须在实践中模拟测试类的每个依赖项。如果创建,使用或感知很复杂,那么是的。如果它直接依赖于某些不需要的外部资源,例如DB,网络或文件系统。

但如果这些都不是问题,IMO可以直接使用它的实例。由于您已对其进行了单元测试,因此您可以合理地确定它是按预期工作的,并且不会干扰更高级别的单元测试。

我个人更喜欢工作单元测试和简单,干净,可维护的设计,而不是坚持单元测试纯粹主义者的理想设置。

  

每个单元测试仅测试该单元引入的功能,但不测试单元的依赖性。

使用功能测试功能是两回事。

答案 1 :(得分:2)

我也认为直接使用Inner上的实例是可以的。 我的问题是模拟不属于我的代码的外部对象(通过静态库或DLL,有时是第三方提供)。 我倾向于使用相同的类名重写模拟DLL或库,然后以不同方式链接以进行测试。修改外部依赖项的头文件以添加“虚拟”似乎是不可接受的。 有没有人有更好的解决方案?