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

kir*_*kun 12 c++ unit-testing mocking googletest

假设我有课程

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

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

    void callInner();
};
Run Code Online (Sandbox Code Playgroud)

适当的单元测试说我应该进行测试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();
};
Run Code Online (Sandbox Code Playgroud)

所以,在生产代码中,我会使用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;
Run Code Online (Sandbox Code Playgroud)

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

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

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

Pét*_*rök 6

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

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

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

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

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