【问题标题】:How best to unit test a class which uses boost::asio::yield_context?如何最好地对使用 boost::asio::yield_context 的类进行单元测试?
【发布时间】:2014-08-06 00:44:03
【问题描述】:

我有一个使用 boost:asio::yield_context 的类,我想知道如何最好地对其进行单元测试。我得到的课程的简化版本:

class Foo {
public:
  void Read(boost::asio::yield_context context) {
      my_scheduler->WaitOnEvent(BUFFER_HAS_DATA, context);
      <...snip...>
      callback(data);
  }

  void Write() {
       // write to buffer
       my_scheduler->FireEvent(BUFFER_HAS_DATA);
  }

  void Start() {
     my_scheduler->Spawn(boost::bind(&Foo::Read, this, _1));
  }
<...snip...>
};

我已经编写了自己的“调度程序”,它包含了 boost asio 功能,所以我有机会在调用实际到达 asio 之前拦截它们。测试是确定性的很重要,所以我希望能够让测试只使用一个线程(所以永远不要真正调用 boost::asio::spawn)并理想地使用类似这样的代码同步测试这个类:

void do_test() {
    <...snip...>
    unsigned int num_callbacks = 0;
    auto callback = [&num_callbacks] (data) {
        ++num_callbacks;
    }
    foo->SetCallback(callback);

    for (int i = 1; i <= 5; ++i) {
        foo->Write();
        foo->Read(); // What would I need to pass here?
        assert(num_callbacks == i);
    }        
}

如果我手动创建一个 basic_yield_context,我能否在测试中将它传递给 Foo::Read 并让它按预期工作?如果是这样,我对 basic_yield_context ctor 在这种情况下实际寻找的内容有点困惑。如果这不起作用,我真的对测试这种代码的更好策略很感兴趣,最好的方法是什么?

谢谢!

【问题讨论】:

    标签: c++ unit-testing boost boost-asio


    【解决方案1】:

    我的建议是使用模拟对象来模仿yield_context 的界面。

    您可以尝试使用许多库进行模拟。

    一种可以用来提升的好方法是海龟:

    http://turtle.sourceforge.net/

    GoogleMock 是另一种可能性:

    https://code.google.com/p/googlemock/

    为了使用这些模拟库之一,您必须修改您的接口以适应模拟。我认为在这种情况下您最好的选择是实现 ReadMyScheduler::WaitOnEvent 的方法,因此上下文参数的类型是模板参数(即鸭子类型):

    class Foo {
    public:
    
        //! This would be a `duck-type` interface.
        template <typename YieldContext>    
        void Read(YieldContex context) {
            //! You would also need one for your my_scheduler type's call to WaitOnEvent.
            my_scheduler->WaitOnEvent(BUFFER_HAS_DATA, context);
            <...snip...>
            callback(data);
        }      
        <...snip...>
    };
    

    【讨论】:

    • 为了做到这一点,我的理解是我必须让 mock 派生自 yield_context,并且由于没有接口(也没有默认 ctor),我必须初始化yield_context,这意味着我需要所有必要的参数来创建yield_context——我希望有办法避免这种情况。除非我误解了?
    • 谢谢,布兰登。我有一个带有签名的方法:void Read(boost::asio::yield_context context),我需要在那里传递一些 boost::asio::yield_context 类型的东西。例如,Google mock 具有从被模拟的类型派生的 mock,因此需要满足非默认 ctor。我错过了什么?
    • 你是对的。它确实需要从中派生。此外,您的签名将切片任何此类派生对象,因为它按值传递上下文。假设您可以控制这些签名,您有几个签名选项。您可以通过 const 引用使它们获取上下文,也可以将它们更改为获取模板参数(如鸭子类型)。
    • 实际上,如果您可以为Read 和调度程序的方法WaitOnEvent 创建一个“duck-typing”接口,那似乎可以解决这两个问题。
    • 嘿布兰登,鸭子打字的东西肯定会奏效,好主意。我希望有一种更直接的方法,但也许这是我能做的最好的。我还想知道是否做一些事情,比如让 Foo 类保持原样,但不将任何线程附加到 io_service 并从我的测试中显式调用 run 可能是另一种好方法。不确定哪种策略在这里最好,我将不得不尝试它们。鸭子打字是一个很好的工具,我敢打赌它在其他情况下也能很好地工作。
    【解决方案2】:

    将布兰登的答案标记为正确的答案为 1) 这是我得到的唯一答案 :) 和 2) 它比我能想出的任何答案都更通用。

    我最终没有使用鸭子类型方法,因为它不能很好地满足我编写的 asio 包装器的需求。我最终做的实际上是继续让类使用真正的 io 上下文和真正的 yield 上下文,但不将线程附加到 io 上下文。我建立了预定事件的伪“时间线”,然后根据测试需要将它们播放出来,以便结果始终是确定性的。这听起来可能有点做作,但在我正在处理的限制范围内,它是最有效的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-30
      • 2015-01-15
      • 2014-03-07
      • 1970-01-01
      • 1970-01-01
      • 2020-05-23
      • 2012-08-31
      • 1970-01-01
      相关资源
      最近更新 更多