在 Swift 中对私有变量进行单元测试
Posted
技术标签:
【中文标题】在 Swift 中对私有变量进行单元测试【英文标题】:Unit testing private variables in Swift 【发布时间】:2019-09-27 05:54:41 【问题描述】:我很难掌握如何在我的所有字段都是私有的类中实现单元测试。
该类正在使用 BLE 和 CoreLocation 计算用户的位置 - 不是那么重要。我有一个协议,当找到一个新位置时,我会调用它,所有符合该协议的类都将收到一个房间 ID 和房间名称。那么,这意味着我班级中的所有字段实际上都是私有的,因为是的,没有理由任何外部班级都应该访问它们吗?但这也意味着我无法在该类中测试任何内容,即使有很多我想测试的功能。我的意思是,我可以将变量设置为内部变量而不是私有变量,但是仅仅为了单元测试而这样做似乎是错误的。我听说过依赖注入,但这似乎需要付出很多努力。
例如我有这个功能:
private var beacons: [AppBeacon] = []
private var serverBeacons:[Beacon] = []
private func addBeacons(serverBeacons: [Beacon])
for beacon in serverBeacons
let beacon = AppBeacon(id: beacon.id, uuid: beacon.uuid, building: beacon.building, name: beacon.name)
beacons.append(beacon)
例如,我无法测试信标数组是否真的按我的意愿填满。我的类的公共特性基本上是一个名为 startLocating 的函数,结果是房间 ID 和名称,我知道在黑盒测试中模仿了哪些单元测试(对吗?)我不应该关心中间步骤,但老实说,我应该说这么多功能,没关系吗?并假设我确实用我选择的一些 rssi 值填充了信标,实际的位置算法在 node.js 服务器上执行,所以老实说我不知道要测试客户端什么?
这是经典的 MVC,在截止日期之前我无法更改它的架构,所以我不知道从这里开始的最佳方式是什么?只是不测试功能?使字段内部而不是私有?我们在服务器端对算法本身进行测试,所以测试房间 id 是否是预期的房间 id,已经测试过了。
我在另一篇文章中读到以下内容:
“单元测试的定义是黑盒测试,这意味着你不关心你测试的单元的内部。你主要感兴趣的是根据你给它的输入看看单元输出是什么单元测试。 现在,通过输出我们可以断言几件事:
方法的结果 作用于对象后的状态, 与对象所具有的依赖项的交互在所有情况下,我们只对公共接口感兴趣,因为它是与世界其他地方通信的接口。 私人物品不需要仅仅因为任何私人物品都被公共物品间接使用而进行单元测试。诀窍是编写足够的测试来锻炼公共成员,以便完全覆盖私有成员。
另外,要记住的一件重要事情是单元测试应该验证单元规范,而不是它的实现。验证实现细节增加了单元测试代码和被测代码之间的紧密耦合,这有一个很大的缺点:如果被测实现细节发生变化,那么单元测试很可能也需要改变,这降低了拥有的好处对那段代码进行单元测试。”
因此我基本上理解为我不应该对此进行单元测试?
【问题讨论】:
通常你有使用/读取你的属性的函数,所以你可以对它们进行单元测试。测试beacons
属性的示例,您测试一个使用该属性的函数,并在调用该函数进行测试之前通过调用addBeacons
来设置它。
是的,但假设我调用 addBeacons,我不能只说:addBeacons();
XCTAssertEqual(beacons.first.id, "id")
,因为信标是私有的,所以我根本无法访问该属性。
这根本不是我说的,你必须编写一个单元测试来测试从beacons
属性读取的函数,以测试它是否已被addBeacons
正确设置。您通过测试类的公共 api 来测试一个类,并且该公共 api 由所有公共函数和属性组成
很抱歉,我可能完全脑残了,但我只是不明白当信标是私有的时从beacons
阅读是什么意思?甚至 addBeacons 都是私有的?在我的测试套件中,我什至不能打电话给addBeacons
。我可以调用的是startLocating()
,它确实调用了另一个函数,然后调用了 addBeacons(),但是该类中所有函数的结果本质上是一个房间名称和房间 ID,它不会验证 addBeacons()工作正常与否?我不知道我是否只是愚蠢,但你能详细说明一下吗?
抱歉,我错过了 addBeacons
也是私有的,但我所说的为公共 api 编写测试仍然有效。
【参考方案1】:
首先,您引用的“单元测试”的定义非常不寻常:我所知道的文献中的所有定义都考虑对玻璃盒/白盒测试方法进行单元测试。更准确地说,这样的单元测试实际上既不是黑盒也不是白盒——它是用于设计测试用例的方法,这会有所不同。但是没有理由不将白盒测试设计技术应用于单元测试。事实上,一些白盒测试设计技术只有在应用于单元测试时才有意义。例如,当你研究单元测试时,你会遇到很多关于不同代码覆盖率标准的讨论,比如语句覆盖率、分支覆盖率、条件覆盖率、MC/DC——对于所有这些,了解实现是必不可少的代码细节,并在测试用例设计期间考虑这些实现细节。
但是,即使考虑到这一点,它对于您的特定情况也没有太大变化:代码中的变量仍然是私有的 :-) 但是,您正在以一种过于严格的方式处理测试问题:您尝试测试函数 addBeacons
本身,与该组件中的其他函数完全隔离。
要测试addBeacons
,您的测试用例不仅应包含对addBeacons
的调用,还应包含其他一些函数调用,其结果将显示对addBeacons
的调用是否成功。例如,如果您还具有按名称或位置查找信标的功能,您将首先调用 addBeacon
并检查是否成功,调用其中一个函数以查看是否会找到您添加的信标。这两个调用将属于同一个测试用例。
【讨论】:
【参考方案2】:如果您有一个 private var
可以帮助您编写单元测试,请将其更改为 private(set) var
以便可以读取(但不能更改)。
暴露内脏可能会让您感到困扰。如果是这样,则可能有另一种类型等待从视图控制器中提取。
【讨论】:
以上是关于在 Swift 中对私有变量进行单元测试的主要内容,如果未能解决你的问题,请参考以下文章
在 Swift 测试驱动开发中对 @ObservableObject 进行单元测试