【问题标题】:Patterns for unit testing a C++ method that makes a standard library call用于对进行标准库调用的 C++ 方法进行单元测试的模式
【发布时间】:2011-04-25 14:54:33
【问题描述】:

我正在编写一个 C++ 类来包装套接字(我知道有很好的库可以用来做这件事——我正在自己动手练习):

class Socket {
public:
  int init(void); // calls socket(2)
  // other stuff we don't care about for the sake of this code sample
};

这个类又被其他几个人使用,我知道我可以通过子类化和模拟对googlemock 进行单元测试。

但是我想开发这个类test first,目前有点卡住了。我不能在 C 标准库(即socket.h,在这种情况下)上使用 googlemock,因为它不是 C++ 类。我可以围绕我需要的 C 标准库函数创建一个精简的 C++ 包装类,例如

class LibcWrapper {
public:
   static int socket(int domain, int type, int protocol);
   static int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
   static int listen(int sockfd, int backlog);
   static int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
   static ssize_t write(int fd, const void *buf, size_t count);
   static int close(int fd);
};

现在我可以模拟它并对我的Socket 类进行单元测试(现在可能需要将其重命名为Network 或类似名称)。 LibcWrapper 也可以用于其他类,并且它本身不需要进行单元测试,因为它只是提供了一堆类方法。

这对我来说开始听起来不错。我是否已经回答了自己的问题,或者是否存在用于测试驱动这种 C++ 开发的标准模式?

【问题讨论】:

  • googlemock cookbook 在你的情况下建议类似的东西。
  • @Beduin:回答这个问题,我会投赞成票。 :)
  • 我最近发现,使用集成 (?) 测试来验证此类类通常更有意义且更节省时间。例如,您可以通过分叉一个进程轻松验证您的包装器是否处理连接重置,该进程 1) 将原始套接字连接到包装器,在主进程中运行,然后 2) 立即发生段错误,导致连接重置。
  • @Andres:我确实有一个功能测试来验证使用我的套接字的代码,但我在这里要测试的是代码单元(即Network::init())是否调用socket()Network::listen() 调用bind()listen() 等等,所以我认为除了功能测试之外还需要单元测试。
  • @Josh Glover 在这种情况下,我同意 100%。 :)

标签: c++ unit-testing


【解决方案1】:

我可能会通过使用套接字接口(即基类)并实现该基类的测试版本来模拟它。

您可以通过多种方式做到这一点,例如,最简单的方法是根据 C++ 接口指定整个套接字 API。

   class ISocket
   {
   public:
        virtual int socket(int domain, int type, int protocol) = 0;
        virtual int bind(int sockfd...) = 0;
        // listen, accept, write, etc            
   };

然后提供一个通过 BSD 套接字库工作的具体实现

   class CBsdSocketLib : public ISocket
   {
   public:
       // yadda, same stuff but actually call the BSD socket interface
   };


   class CTestSocketLib : public ISocket
   {
   public:
       // simulate the socket library
   };

通过针对界面进行编码,您可以创建测试版本来做任何您喜欢的事情。

但是,我们可以清楚地看到,这第一关相当奇怪。我们正在包装整个库,从描述对象的意义上说,它并不是一个真正的类。

您宁愿考虑插座和制造插座的方法。这将更加面向对象。按照这些思路,我会将上面的功能分为两个类。

   // responsible for socket creation/construction
   class ISocketFactory
   {
        virtual ISocket* createSocket(...) = 0; // perform socket() and maybe bind()
   };

   // a socket
   class ISocket
   {
         // pure virtual recv, send, listen, close, etc
   };

现场使用:

   class CBsdSocketFactory : public ISocketFactory
   {
      ...
   };

   class CBsdSocket : public ISocket
   { 
      ...
   };

用于测试:

   class CTestSocketFactory : public ISocketFactory
   {
   };

   class CTestSocket : public ISocket
   {
   };

并将 BSD 库调用分成这两个具有各自职责的不同类。

【讨论】:

  • 我非常喜欢这种方法。谢谢!
【解决方案2】:

我也使用过这种技术。请注意,Google Mock 并不完全支持模拟静态函数。 The FAQ explains that you should use an interface 带有可以以通常的 Google Mock 方式覆盖的虚拟方法。

【讨论】:

  • 谢谢!我接受 Doug T 的例子仅仅是因为他提供的所有细节,但你的例子肯定会获得 googlemock FAQ 链接的支持。 :)
【解决方案3】:

Google Mock cookbook 在您的情况下提出了类似的建议。

【讨论】:

    猜你喜欢
    • 2021-10-15
    • 2017-02-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多