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

时间:2010-09-04 11:04:57

标签: objective-c cocoa unit-testing sockets ocmock

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

我的SieveClient对象依赖于网络通信的另外两个对象:CocoaAsyncSocket和我自己的SaslConn对象,它是我在Cyrus SASL库中的包装器,用于处理身份验证方法。为了测试,我需要用模拟对象替换它们。我将使用OCMock框架。我不太清楚如何做到这一点,因为SieveClient对象需要自己创建这些对象。现在我覆盖该对象的(私有)setter,以便始终使用OCMocks partialMockForObject:方法安装我的模拟对象。但这对我来说不合适。有什么想法可以更好地解决这个问题吗?

我遇到的另一个问题是套接字本身。为了能够测试协议细节,我需要一种从套接字返回预定义测试数据的方法。我想我可以使用OCMock机制伪造套接字的返回值。但是由于CocoaAsyncSocket提供了许多不同的方法来从套接字读取数据,我必须确切地知道协议对象使用哪种顺序。我不希望我的单元测试依赖于我的协议对象的实现细节。那我该怎么办?手动为套接字类实现模拟对象?这似乎并不重要,所以我可能也需要进行单元测试。这是个好主意吗?

我读过如果有些东西很难测试,那么它的设计可能也不是很好。但我不知道我怎么能做得更好,因为困难的部分在于我必须做的插座交互。

如果您想查看代码,可以在Bitbucket找到它:SieveClient.mSieveClient.h

编辑:依赖注入

所以我读到了依赖注入,我想我会用它来将AsyncSocketSaslConn个对象放到我的SieveClient对象中。我将更改我的构造函数以接受这些对象并使用它们。由于这个类的用户通常不关心套接字和SASL对象,所以我将添加一个工厂方法(以方便构造函数的形式),只创建这些对象并将它们传递给构造函数。

但这只能解决我测试问题的第一个(也更容易)部分。

3 个答案:

答案 0 :(得分:7)

  

但后来我拒绝了这个想法,因为它   不会太帮助。我可以测试一下   SieveClient对象更容易,   真正。但后来我也一样   测试新对象的问题。   在我看来这只是放   以后麻烦了。特别   因为我什么都没有,我可以重复使用   新课程。

这不是同一个问题。

我假设您需要SieveClient来内部控制其他对象的实例化,因为它是您不希望公开的API的一部分。如果这就是原因,通过分离它们,您不再需要相同的需求,因为您可以使用 SieveClient控件进行绑定,而另一部分执行协议接收实例。使用

通过执行上述操作,您可以将模拟对象移交给协议实现那些模拟将有你可能需要的任何期望。如果你发现它最终会让它过于参与,那么你可能需要重新调整职责,这通常会导致更简洁/更简单的协议实现(如果你发现需要进行这些单元测试)。

上面说过,您还需要考虑您尝试测试的代码是否尽可能专注于协议,并且没有任何额外的元素。如果是这种情况,它就不适合进行单元测试,因为它的唯一责任是与外部系统的交互。我决定这个系统中的协议规范有多重要,如果它全部与外部系统集成,我会把它看成是一个集中的集成测试而不是真正的外部系统,并且与单元测试保持分离< / strong>(因此它不会影响运行系统其余部分的单元测试所需的速度)。


由于编辑重新阅读问题后,我必须强调我上面提到的关于集中集成测试的内容。你问:

  

但是自从CocoaAsyncSocket提供   许多不同的方法来读取数据   从插座我必须确切知道   正在使用的协议   对象的顺序。我不想要我的   单元测试依赖于   我的协议的实现细节   宾语。那我该怎么办?   为套接字实现模拟对象   手工上课?这似乎是微不足道的,   所以我可能需要进行单元测试   那也是。这是个好主意吗?

如果您正在处理一个非常复杂的对象,并且该对象完全是关于边界之外的集成,那么通常最好将其作为单元测试的一部分来避免。在那种情况下,您需要一个集中的集成测试/来打击真正的外部系统。 这并不意味着代码的其余部分的所有单元测试,点击外部系统,只是使用该对象的非常简单的代码/类单元

在你的场景中,很可能是这样的对象是SieveClient,在这种情况下忘记了那段代码的单元测试。您想要做的是在测试使用它的代码时模拟SieveClient。另一方面,如果您发现SieveClient远不止于此,那么您希望添加一个简化这些通信方面的类,这就是您在测试SieveClient时所模仿的内容,以及您针对的重点集成测试。

这种类型的测试是一种非常有效的方法,可以确保与外部交互的代码按预期工作,因为这是所涉及的类和测试的焦点。 如果外部系统上的某些内容开始以不同的方式工作,您会清楚地注意到它 - 而不是将其与应用程序逻辑混合在一起,或者最糟糕的是根本没有进行测试。

答案 1 :(得分:2)

你能把你正在做的事分成两部分,其中一部分是抽象协议,另一部分是对套接字的绑定?然后,您可以更轻松地测试抽象协议,并将绑定测试集中在是否正确调用任何连接的抽象协议的方法/操作。

抽象地说,您将减少代码各部分之间的耦合。这增加了可测试性,代价是总体复杂性有所增加(虽然不是太糟糕,因为你通过分离关注来获得更好的工具来管理它)和一些潜在的性能下降(尽管大多数系统没有太大的问题;你的计算机比它的I / O子系统快得多。)

答案 2 :(得分:2)

不要太聆听教条。寻找可能有效的最简单的东西,也用于测试。 (免责声明:我确实知道TDD,但我不知道目标C)。

要让SieveClient在生产代码中创建SaslConn,但在测试中使用模拟,可以使用dependency injection。向SieveClient添加setter方法以传入factory(作为对象或函数,具体取决于Objective C允许的内容),SieveClient将使用它来生成SaslConn SaslConn而不是单独制作它们。测试代码提供了一个测试工厂,可以清除嘲笑。使SieveClient的生产案例代码移动到另一个工厂进行独立的单元测试,或者如果它太容易破坏,当工厂设置器是SaslConn时,它仍然是SaslConn内的默认行为。不叫。

测试网络客户端代码的最简单方法是implement or re-use a mock server。不要嘲笑SaslConn中的血腥插座细节;相反,在测试中编写SASL服务器。您foo可以与之交谈的事实对于为该模拟服务器提供测试有很长的路要走;换句话说,bar和模拟服务器是彼此的单元测试。 (是的,不是纯粹主义意义上的“单位”,但没有人关心。)

最后,我对于难以测试代码设计糟糕的规则感到复杂。这取决于。您应该设计代码,以便它(在调用者代码中)易于使用且易于修改。单元测试只是达到这些目的的一种手段:它们是您将要编写的第一个调用者代码,它们让您确信在进行更改时不会搞砸。不要让特定的框架或方法twist and maim your design超过TDD的好处。特别是,基于期望的模拟框架(如OCMock)让write brittle tests过于简单,就像“我希望方法{{1}}被调用3次,然后方法{{1}}一样用这样的论点来称呼“。而不是使用错误的工具,写自己的!