单元测试和私有变量

36

我正在为一个公共方法编写BDD单元测试,该方法更改了一个私有属性(private var),因此我想编写一个expect()并确保它被正确设置。由于它是私有的,我无法从单元测试目标中访问它。

对于Objective-C,我只需添加扩展头即可。在Swift中有类似的技巧吗?请注意,该属性有一个带有一些代码的didSet()。

1个回答

47
注意,Swift 2添加了@testable属性,可以使内部方法和属性可用于测试。有关更多信息,请参见@JeremyP的下面评论。
不。在Swift中,private是私有的。编译器可以利用这一点进行优化,因此,根据您在该文件中实际使用该属性的方式,编译器可以删除它、内联它或执行任何其他操作,以根据代码实际行为给出正确的结果。(无论优化器今天是否真的很聪明,都允许这样做。)
当然,如果您声明您的类是@objc,那么您就可以打破这些优化,并且可以使用ObjC来阅读它。还有一些奇怪的解决方法,可以让您使用Swift调用任意@objc公开方法(比如零超时NSTimer)。但别这样做。
这是一个经典的测试问题,而经典的测试答案是不要这样测试。不要测试内部状态。如果从外部看不出发生了什么,那么就没有什么可测试的。重新设计该对象,使其可以通过其公共接口进行测试。通常这意味着组合和模拟。
这个问题最常见的版本可能是缓存。很难测试某些东西是否被缓存,因为唯一的区别可能是它被检索得更快。但它仍然是可测试的。将缓存功能移动到另一个对象中,并让您要测试的对象接受自定义缓存对象。然后,您可以传递一个记录是否进行了正确缓存调用(或网络调用、数据库调用或任何内部状态)的模拟对象。
基本上答案是:重新设计使其更容易测试。
好吧,但如果你真的、真的需要它……怎么办?好吧,这是可以做到的,而不会破坏世界。

在待测试的文件中创建一个函数来暴露你想要的东西。不是方法,而是自由函数。然后你可以把这个辅助函数放在 #if TEST 中,并在测试配置中设置 TEST。理想情况下,我会让这个函数实际测试你关心的东西,而不是暴露变量(在这种情况下,也许你可以让函数成为内部或甚至公共的)。但无论哪种方式都可以。


谢谢你的回答。我一直在等待更多的选项,但它们都失败了,所以我又回到了原点。我希望苹果能在未来的Swift版本中提出更好的处理方式。我不是将我的私有类成员包装到另一个对象中仅仅为了使其可测试的忠实粉丝。在某些情况下,我会得到只包含一个属性的对象。但我理解你的想法。我有一种感觉,许多开发人员将使属性公开,只是为了懒散地解决问题。 - Robert Gummesson
4
顺便提一句,Swift 2.0 中已经不是这样了。@testable 属性用于单元测试时会将内部方法公开。 - Jeef
9
这是一个非常好的答案,除了第一句话不正确。 @testable 不会使私有变量可用于测试,只有内部变量可以。因此,Swift 2并没有让这个问题变得微不足道,这样做是有充分理由的。如果没有办法从外部看到内部状态的影响,那还有谁在乎它是什么呢? - JeremyP
12
不想挑起争端,只是想提供一个不同的观点 - 我其实有点不同意这个说法。在我看来,绝对地说私有状态或方法永远没有用处是一种过度概括。 - danny
我使用过的一种模式(我相信很多人会讨厌这个,但这是一个选项)是不使用实际的私有方法,而是在变量前加上“_”表示它们是私有的(就像在Python中所做的那样,它没有私有概念)。然后,您可以根据需要测试或不测试它们,并且仍然有指示它是私有的。 - danny

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接