C++中的模拟和依赖注入

5
我在使用C++中的googlemock和依赖注入进行单元测试时遇到了困难。Mock和依赖注入可以显著地简化代码测试,但它们严重依赖于虚方法。虽然其他语言中的类默认使用虚方法,但C++不是这种情况。我正在使用C++创建一个低开销的性能测量框架,因此使每个类都继承自一个接口(带有纯虚方法)并不是一个理想的选择。
具体来说,我在测试包含对象集合的类时遇到了问题,例如以下内容:
struct event_info { /* ... */ };

template<typename Event>
class event_manager {
public:
  event_manager(const std::vector<event_info>& events) {
    std::transform(begin(events), end(events),
        std::back_inserter(events_),
        [](const event_info& info) { return Event{info}; });
  }

  void read() {
    for (auto& e : events_)
      e.read();
  }

  // ...

private:
  std::vector<Event> events_;
  // ...
};

为了测试这个类,我可以执行以下操作:
class mock_event {
public:
  MOCK_METHOD0(read, void());
};

TEST(event_manager, test) {
  event_manager<mock_event> manager;
  // ...
}

但是这样做不起作用,因为我无法为模拟对象设置期望,并且来自googlemock的模拟对象不可复制(因此,调用std :: transform无法编译通过)。
为了解决这个问题,在测试时我可以使用指针代替(例如,event_manager<mock_event*>),并将工厂传递给event_manager构造函数。但是,由于诸如e.read()之类的调用(在测试时应该改为e->read()),所以这不会编译通过。
然后,我可以使用类型特征创建一个方法,如果给定一个引用,它只返回该引用,如果给定一个指针,则对指针进行解引用(例如,dereference(e).read())。但是,这只会增加大量复杂性,而且看起来不是一个好的解决方案(特别是如果需要对包含对象集合的所有类进行测试)。
因此,我想知道是否有更好的解决方案,或者是模拟和依赖注入不太适合C ++技术。

这是一个好问题!我期待着答案。在我的理解中,也许你可以尝试对event_manager进行一些修改,使用std::vector<std::unique_ptr<Event>>,也许这样可以解决问题。但我没有尝试过,所以不确定是否有效。 - Mine
使用unique_ptr和工厂(或更改接口以允许用户添加unique_ptr<Event>)应该可以解决问题。但是我需要在生产中存储指针(而不仅仅是测试期间),我正在寻找避免这种情况的解决方案。 - betabandido
是的,我的意思是您可以在生产和测试代码中存储指针(unique_ptr)。这应该不是问题。 - Mine
从这个链接中可以看出,你可以为你的模拟对象提供复制语义,但你必须自己编写代码。这意味着你仍然可以使用模板依赖注入。 - rozina
1个回答

1
我假设在决定您的应用程序无法容忍经过指针解引用执行虚函数调用的开销之前,您已经使用一个简单的类来模拟您的标准用例进行了适当的性能测量。
如果您阅读gmock文档,他们有一个“高性能模拟”部分,在其中展示如何在生产代码中使用模板来允许模拟非虚函数。
我认为代码(生产或测试)的第一条规则是尽可能保持代码简单,因此我并不认为改变生产代码以使用模板来进行测试是有说服力的(尽管另一方面,我完全赞成使用TDD作为我的生产代码设计的批判和指导)。
因此,看起来您的应用程序需要另一个模拟框架,可以在链接时而不是运行时执行模拟。
请查看cpputest和cppumock(https://cpputest.github.io/),它能够模拟C自由函数和C++非虚方法。
cpputest/cppumock的代价是需要比gmock更多的样板代码,但它非常好。

是的,对于一些短函数来说,这会有很大的影响。我肯定会看一下cpputest。 - betabandido

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接