【问题标题】:How to unit test a piece of code that relies on Asio?如何对依赖 Asio 的一段代码进行单元测试?
【发布时间】:2021-02-06 05:29:12
【问题描述】:

我有一个封装 Asio 的类。它旨在模拟域和 tcp 套接字上的通信,但我对自动化单元测试感到茫然。我查看了FakeIt,但它只测试虚拟方法,GoogleMocks suggests 模板化我的代码,这样我就可以通过 MockAsio 实现进行单元测试和生产中的真实 Asio。

还有其他方法可以对网络代码进行单元测试吗?伪造域和 tcp 套接字而不是运行整个堆栈?如果我使用 GoogleMock,为什么要使用使用 GoogleMock 的类而不是我自己的实现来满足我的需要?

【问题讨论】:

  • 好吧,如果您依赖 ASIO,您将不再测试小“单元”。单元测试框架作为测试非单元测试的工具变得越来越难
  • 我想模拟 asio 的输出,以便我可以专注于单元。
  • 我经常mock I/O对象或者他们的I/O服务来对应用协议进行单元测试(见官方custom service example)。在测试计时器时,还可以自定义 TimeTrait(请参阅 here。对于测试 I/O,我经常发现编写一组离散的 minimal reproducible examples 更有价值,允许发生系统调用,从而掩盖操作系统行为通过模拟 I/O 对象。
  • 如果我理解正确,您可以为 IO 服务模板化吗?
  • @ruipacheco 每个 I/O 对象都有一个模板参数,指示它将使用的 I/O 服务(不是 io_service 类)。例如basic_stream_socket 的第二个模板参数是StreamSocketService。 I/O 服务的句柄可通过use_service 获得。这有助于模拟特定的 I/O 行为,而无需直接访问 asio::socket

标签: c++ unit-testing sockets network-programming boost-asio


【解决方案1】:

我最近遇到了同样的问题。由于 Asio 服务的方法(例如套接字的 read_some)通常不是虚拟的,因此简单的依赖注入是不可能的。据我了解,有两种可能的方法,这两种方法都在Google Mock Cook Book进行了讨论:

高性能依赖注入(鸭子类型)

讨论here

这是@ruipacheco 在他的问题中已经提到的选项。

此选项需要模板化您的类,但它引入的代码开销最少。

例如,如果您的类使用 Asio tcp 套接字,则构造它的实例如下所示:

asio::io_context io_context;
MyClass<asio::ip::tcp::socket> my_class(io_context);

接口代码

讨论here

这或多或少是@NiladriBose 所建议的。

此选项需要为每种服务类型(套接字、计时器等)编写一个 Asio 接口和一个 Asio 具体适配器! 尽管如此,它是最通用和最健壮的一个(它不需要像上一个选项那样模板化您的类)。

例如,如果您的类使用 Asio tcp 套接字,则构造它的实例如下所示:

asio::io_context io_context;
AsioSocket socket(io_context);
MyClass my_class(socket);

工厂增强

如果您的类使用多个 Asio 服务实例(多个套接字、计时器等),那么创建一个抽象的 Asio 服务工厂可能会更好。 该工厂将在其构造函数中接收io_context,并导出make_socketmake_timer 等...方法。

然后,构建您的类的实例将如下所示:

AsioFactory asio_factory(io_context);
MyClass my_class(asio_factory);

最后,关于:

如果我使用 GoogleMock,为什么要使用使用 GoogleMock 的类而不是我自己的实现来满足我的需要?

请参阅this 以了解模拟对象和假对象之间的区别。一般来说,我认为模拟对象需要更少的努力。 另请参阅 this 以了解如何将 Google 模拟类与假类合并。

【讨论】:

    【解决方案2】:

    我假设您想模拟您的应用程序使用的 ASIO 包装类。如果我的假设是正确的,那么说包装器有一个接口(过于简单 - 但大多数模拟框架需要一个纯抽象,包括 gmock) -

        class Iasio
        {
            virtual ~Iasio()
            {
    
            }
            virtual void send(std::vector<unsigned char> dataToSend) = 0;
            virtual std::vector<unsigned char > rcv() = 0;
        };
    

    那么你有两个选择—— 1)使用模拟框架进行模拟,并在您的单元测试中使用模拟(使用构造函数或访问器注入将模拟注入到使用它的类中)。对于每个单元测试场景,您需要设置模拟对象以返回您的预期数据。

    2) 第一个选项有时可能比编写自己的 test mock 更麻烦,在这种情况下,编写自己的 test mock 给你更多的控制权是完全可以接受的。我说更多控制是因为模拟框架是通用的,它们可以在最常见的场景中提供帮助,但复杂的场景可能需要定制的测试假人/模拟。

    【讨论】:

    • > 我假设你想模拟你的应用程序使用的 ASIO 包装类。我想模拟 asio,以便测试我的包装类。
    • 得到了你。首先 asio 库 api 是什么样的,我们在谈论 boost::asio 吗?其次,我认为如果您准备进行(包装类的)重构,那么您应该将 asio api 抽象到简化接口后面,就像我的回答中一样,使用外观模式之类的东西。咬住这颗子弹可以让你头疼!模拟对于简单的纯抽象接口来说是小菜一碟。
    • 但这需要我编辑 Asio 的代码以使方法虚拟化,而我做不到。
    • 不,我的意思是不直接访问 ASIO 代码,将其隐藏在具有更简单 API 并为您提供更多控制权的外观或代理后面。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-05-29
    • 2014-01-27
    • 2010-10-04
    • 1970-01-01
    • 1970-01-01
    • 2020-08-02
    • 1970-01-01
    相关资源
    最近更新 更多