作者:NathanSun, iOS开发者,目前就职于字节跳动音乐团队
Sessions: https://developer.apple.com/videos/play/wwdc2020/10221/
这个 session 基于一个基础概念 - 测试反馈环,如下图所示。
这个测试反馈环其实并不难理解,环的一开始是写测试代码,然后跑测试用例,最后是解读测试结果。你可以根据测试结果决定是继续写更多的测试还是直接发布。很显然,应当让这个环的周期越短越好,因为这就意味着你能更快的拿到测试结果,也就进而意味着你可以更快的发布你的 feature。
下面我们将讨论 Xcode 12 中一些新的特性,这些新的特性能保证你的测试总能顺利结束,我们也将讨论怎么利用这些诊断报告来找到阻碍测试正常进行的原因,最后将讨论怎么通过并行测试更快地拿到测试结果。
保证测试正常结束
我们都会有遇到测试 hang 住,然后永远执行不完的情况,这会让我们非常不爽,遇到了只能取消测试任务。通常总结下来测试 hang 住这是由以下原因造成的:
死锁,当两段代码都在等待对方先完成时发生
是真的执行很慢
没有选择恰当的 timeout 时间
主线程占用太多 CPU 时间
为了避免 hang 的问题,Xcode 增加了一个新的选项,即允许执行时间 (Execution Time Allownace), 这个是指单个测试能执行的最大的时间。当测试超过这个给定的时间,Xcode 将会抓取 spindump,然后杀掉 hang 住的测试,继续跑剩下的测试。spindump 是非常有用的,它可以告诉你,每个线程在哪些函数上花费了多少时间;如果测试停住了,从 spindump 也可以看出停在哪些函数上。我们可以从 Terminal 运行 spindump 命令即可抓取,或者如果你更喜欢 GUI 也可以用“活动监视器”来抓取。
默认情况下,一个测试会有 10 分钟的允许执行时间。如果一个测试在 10 分钟之内跑完了,那么时间将重置,下一个测试也将会有 10 分钟的时间。如果你针对所有的测试都想设置更多的执行时间,你可以在 Test Plan 设置项中定制默认配额;如果你只是想针对某个特定的测试用例定制时间的话,你可以用 executionTimeAllowance 这个 API 来设置。注意这个 executionTimeAllowance 实际是一个属性值,单位是秒,而且是以 60 秒为间隔向上取整。具体来说,如果不足 60 秒,将被当做 60 秒钟;如果是 100 秒,那将会是 120 秒。


<<< 左右滑动见更多 >>>
下面我们来看一个具体的例子,比方说我们有下面的测试代码。
testUpdatingSmoothiesFromServer 方法里面会用到一个方法叫 fetchSynchronouslyFromServer,这个方法是一个有问题的方法,永远在执行停不下来。下面我们在 Test Plan 中设置 Test Timeouts = on,然后重新运行一下,超过 10 分钟后,就会抓到一份 spindump。具体如何操作请看下面两张图。


<<< 左右滑动见更多 >>>
那么 10 分钟超时之后,测试结果里面就会有一份 txt 文档,就是我们的 spindump 文件。
双击打开 spindump 文件,可以看到它一般分为两部分,元数据( metadata )以及每个线程正在执行的方法 trace。我们在这个文件快速搜索一下我们刚才那个 fetchSynchronouslyFromServer 方法,发现它又调用了一个 helper method 叫做 performGETRequest,这是一个疑点。我们去代码里面查看后发现 fetchSynchronouslyFromServer 在尝试获取一个锁,而在 performGETRequest 方法里同样在尝试获取同一个锁,这样就形成了死锁。


<<< 左右滑动见更多 >>>
为了解决问题,去掉 performGETRequest 方法里面的锁之后就好了,也就是删掉上图中被选中部分。
有三种办法可以自定义允许执行时间:
第一个是在 Test Plan 设置里面,如下图所示:
第二个就是在 xcodebuild 命令选项里加上-default-test-execution-time-allowance <seconds>
第三个就是通过 executionTimeAllowance 这个API。此三者是有一个优先级顺序的,优先级顺序如下:
为了避免有些测试设置过大或者无限大的默认时间,你也可以设置最大执行时间,这就意味着不管你用上面三个方法设置的值是多大,都不能超过这里设置的最大执行时间。可以 在Test Plan 设置项里面进行设置,如下图所示:
你也可在 xcodebuild 命令选项里设置 -maximum-test-execution-time-allownance <second>。
如何加速测试?
答案是在不同设备上并行测试。通过 xcodebuild 命令进行分布式测试,以类为单元将不同类放到不同的测试设备上进行测试。但是具体某个类会被分到哪台测试机器上,这个是没有办法保证的。如果你的测试依赖某个特定 OS 版本或者机型的话,那么测试可能会失败或者被跳过。如下图所示,从 XCode 12 开始支持对 iOS 设备和 tvOS 设备支持并行跑单元测试和 UI 测试。


<<< 左右滑动见更多 >>>
具体如何操作呢?你只需要在 xcodebuild test 后面加上参数 -parallel-testing-enabled YES, 并且设置目标机型即可。
值得注意的是,仅需要有两个测试设备,那么测试速度将会有30%的提升!
下面针对并行测试给出一些建议:
理想情况下,应该是用同样的一种设备类型和操作系统版本来进行测试,因为测试用例会被不确定地派发到不同机器,这样的话你就可以避免有些测试问题难以重现。
如果确定是要用不同的设备类型或者操作系统版本来测试的话,可以跑业务代码这种对操作系统版本或者设备类型不敏感的代码。
如果想在更多的设备类型和操作系统版本上测试,比如你想在 iOS 13 上和 iOS 14 上都验证你的代码,建议使用并行测试。
推荐阅读
iOS 稳定性:如何在开发中规避安全风险
iOS 稳定性:App 被终止的原因
iOS 稳定性:编写会“失败”的测试
关注我们
我们是「老司机技术周报」,每周会发布一份关于 iOS 的周报,也会定期分享一些和 iOS 相关的技术。欢迎关注。
关注有礼,关注【老司机技术周报】,回复「2020」,领取学习大礼包。
支持作者
这篇文章的内容来自于 《WWDC20 内参》。在这里给大家推荐一下这个专栏,专栏目前已经创作了 108 篇文章,只需要 29.9 元。点击【阅读原文】,就可以购买继续阅读 ~
WWDC 内参 系列是由老司机周报、知识小集合以及 SwiftGG 几个技术组织发起的。已经做了几年了,口碑一直不错。 主要是针对每年的 WWDC 的内容,做一次精选,并号召一群一线互联网的 iOS 开发者,结合自己的实际开发经验、苹果文档和视频内容做二次创作。