12

假设我有课

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

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

    void callInner();
};

适当的单元测试表明我应该对Inner. 然后,我应该对此进行测试,而Outer不是使用真实Inner的,而是使用 a 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;

这避免了样板化和不必要的虚拟调度;但现在我的整个代码库都在该死的头文件中,这使得隐藏源代码实现变得不可能(更不用说处理令人沮丧的模板编译错误和漫长的构建时间了)。

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

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

4

2 回答 2

6

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

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

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

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

使用功能测试功能是两件非常不同的事情。

于 2011-03-24T22:10:01.893 回答
2

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

于 2011-09-20T00:09:06.427 回答