注意您的Swift API(单元测试陷阱)中的协议扩展。
我们都喜欢协议扩展,它是Swift中面向协议编程(POP)最强大的元素之一。 尽管它们具有无可置疑的好处,但在少数情况下,您应该避免使用它们。 在本文中,让我演示一个潜在的陷阱,当您试图对依赖某些协议扩展功能的代码进行单元测试时,API使用者可能会陷入此陷阱。
快速提醒:方法分派
在Swift中,我们有三种方法分配: static , vtable和message分配。 如果您不熟悉此术语,请允许我推荐Riazlab的一篇精彩文章。 简而言之,如果在多个位置(例如,父类或协议扩展)定义了相同的签名,则调度方法使用不同的技术来选择要执行的函数的具体实现。 例如,1)具有从 NSObject
继承的类 将始终使用消息调度,而2)值类型(结构,枚举)将始终使用静态调度。
好的,让我们回到危险的情况下,作为一个API创建者(我们都是API设计者,您还记得John Sundell的演讲吗?),我们遵循最佳实践并为我们的公共API提供协议抽象。 就本文而言,假设我们希望公开一个可能记录详细和错误消息的logger类,如下所示:
解决该方法的一种方法,以验证LogMock.log(_:message)
是否已正确调用。 乍看起来似乎是一个合理的想法,但是这种方法存在一个固有的问题-在System
类测试中,我们才刚刚开始测试由第三方开发人员编写的API中的verbose(message:)
实现。 Logger.verbose(_:String)
潜在的实现更改或错误可能会影响我们的测试结果。 顾名思义,单元测试应该在高度隔离的上下文中验证代码的单个单元(此处为System
类)。
解决这个问题真的很简单。 作为API设计人员,您要做的就是将要在协议扩展中公开的所有函数/变量包含在主协议定义中,例如:
客户流
上面的修复程序专用于API创建者,但是即使您无权修改协议声明,也可以针对您的麻烦进行补救。 您将需要依赖自一个原始协议继承的自定义协议,并包括API设计器仅从协议扩展中公开的所有声明。 同时,在等待API设计者的修复程序时,您不再受到阻塞。
我们讨论了协议扩展,发现测试依赖协议扩展的代码可能很棘手。 幸运的是,有一个非常简单的补救方法–只需在协议定义中包含来自公共协议扩展的函数/变量定义即可。 因此,当您听到自己说:“让我们在协议扩展中实现它”时,您将知道该进行回退的时间了,并确保静态分发不会阻碍客户的单元测试。
尽管具有所有优点,但vtable分派的动态性质与静态 表相比具有一定的性能开销,而静态表不必执行表查找。 但是,在大多数情况下,差异并不明显,因此这听起来是使API可测试的合理权衡。
- dispatch_once过度杀伤了+ ?
- iOS 8自定义键盘
- 如何解决** mach_vm_map(size = 8388608)失败(错误代码= 3)***错误:无法分配区域错误
- NSString boundingRectWithSize返回不必要的高度
- 使用instantiateViewControllerWithIdentifier和performseguewithidentifier有什么区别?
- 任何方式来编码PNG比UIImagePNGRepresentation更快?
- 如何在Sprite Kit中实现鼠标连接?
- UISplitViewController旋转iOS8无法按预期工作
- 如何获取UIImagePickerController所选图像的原始文件名?