代码之家  ›  专栏  ›  技术社区  ›  Sven

如何对互联网协议实现进行单元测试?

  •  6
  • Sven  · 技术社区  · 16 年前

    我决定将单元测试添加到我的项目中,并以测试驱动的方式继续开发。我目前正在为我的ManageSieve客户机对象实现单元测试,我不确定什么是测试这个野兽的最佳方法。

    我的 SieveClient CocoaAsyncSocket 还有我自己的 SaslConn 对象,它是Cyrus SASL库的包装器,用于处理身份验证方法。为了进行测试,我需要用模拟对象替换这些对象。我将为此使用OCMock框架。我不太清楚如何做到这一点,因为SieveClient对象需要自己创建这些对象。现在我覆盖该对象的(私有)setter,以便始终使用ocmock安装我的mock对象 partialMockForObject: 方法。但我觉得这不对。有没有办法更好地解决这个问题?

    另一部分我有麻烦的是插座本身。为了能够测试协议细节Id,需要一种从套接字返回预定义测试数据的方法。我想我可以使用OCMock机制来伪造套接字的返回值。但是自从 提供了许多不同的方法来从套接字读取数据我必须确切地知道协议对象以何种顺序使用哪些数据。我不希望我的单元测试依赖于协议对象的实现细节。我该怎么办?手动实现socket类的模拟对象?这看起来很重要,所以我可能也需要单元测试。这是个好主意吗?

    我读到,如果有些东西很难测试,它可能也不是很好的设计。但我不知道我怎么能做得更好,因为最困难的部分在于与套接字的交互,我必须这样做。

    如果您想查看代码,可以在Bitbucket中找到: SieveClient.m 和 SieveClient.h

    编辑:依赖项注入

    所以我读了关于依赖注入的文章,我想我要用这个来获得 AsyncSocket 和 萨斯尔康 筛选客户端

    但这只解决了我测试问题的第一部分(也是更简单的部分)。

    3 回复  |  直到 16 年前
        1
  •  2
  •   Donal Fellows    16 年前

    你能把你正在做的事情分成两部分吗,一部分是抽象协议,另一部分是对套接字的绑定?然后您可以更容易地测试抽象协议,并将测试绑定的重点放在它是否正确调用任何连接的抽象协议的方法/操作上。

        2
  •  7
  •   eglasius    16 年前

    但后来我拒绝了这个想法,因为 SiveClient对象更容易, 是的。但我也有同样的想法 测试新对象时出现问题。 把这个麻烦留到以后再说。尤其是 因为我没有什么可以重复使用的

    我假设您需要SieveClient在内部控制其他对象的实例化,因为它是您不想公开的API的一部分。如果这就是原因,把它们分开,你就不再有同样的需要,因为你可以有 .

    通过以上操作,您可以 将模拟对象交给协议实现 这些嘲弄会有你可能需要的任何期望 . 如果您发现它最终会导致它过于复杂,那么您可能需要重新调整职责,这通常会导致更干净/更简单的协议实现(如果您发现您需要从执行这些单元测试中得到帮助)。

    上面说,你也需要 考虑一下您要测试的代码是否尽可能集中在协议上,并且没有任何额外的元素。如果是这样的话,那么单元测试就不是一个好的选择,因为它唯一的责任就是与外部系统的交互。我会决定协议规范在这个系统中有多重要,如果它完全是关于与外部系统的集成,我会把它当作一个集中的集成测试,而不是攻击真正的外部系统,并与单元测试分开


    由于编辑的原因,我重新阅读了这个问题之后,我不得不强调我所说的话 集中集成测试 上面。你问:

    但既然CocoaAsyncSocket提供 读取数据的方法多种多样 协议正在使用的 我的协议的实现细节 为套接字实现一个模拟对象 手工上课?这看起来很重要, 那也是。这是个好主意吗?

    这并不意味着所有代码的单元测试,都会涉及到外部系统,只是使用该对象的非常简单的代码/类单元

    在您的场景中,这样的对象很可能是SieveClient,在这种情况下,请忘记对这段代码的单元测试。您想做的是在测试使用它的代码时模拟SieveClient。另一方面,如果您发现SieveClient远不止这些,那么您需要添加一个类来简化这些通信方面,这就是您在测试SieveClient时所模拟的,也是您针对其进行集中集成测试的对象。

    这种类型的测试是确保与外部交互的代码按预期工作的非常有效的方法,因为这是类和相关测试的重点。 如果外部系统上的某些东西开始以不同的方式工作,您会清楚地注意到它,而不是将它与您的应用程序逻辑混合,或者根本不进行测试。

        3
  •  2
  •   DomQ    16 年前

    让 SieveClient 创建其 SaslConn dependency injection . 将setter方法添加到 筛选客户端 factory (作为一个对象或函数,取决于目标C所允许的),哪个 而不是自己做。测试代码提供了一个测试工厂,它提供了mock。生产案例代码 萨斯尔康 的行为或者转移到另一个工厂进行独立的单元测试,或者如果它太简单而无法破坏,则仍然是内部的默认行为 筛选客户端 不调用工厂设置器时。

    implement or re-use a mock server . 不要嘲笑这些血淋淋的插座细节 萨斯尔康 萨斯尔康 萨斯尔康 模拟服务器是彼此的单元测试。(是的,不是纯粹意义上的“单位”,但没人在乎。)

    最后,对于难以测试的代码设计得很糟糕这一说法,我有着复杂的感受。视情况而定。您应该设计您的代码,使其易于使用(在调用方代码中)和修改。单元测试只是达到这些目的的一种手段:它们是您将要编写的第一个调用程序代码,它们让您确信在进行更改时不会出错。不要让特定的框架或方法 twist and maim your design write brittle tests 就像“我期望方法” foo 要调用3次,然后才调用方法 bar 用这样那样的论据来称呼。与其为工作使用错误的工具,不如自己写!