赞
踩
本文还是 adat 项目的延伸,瞄准开发周期的测试环节,主要介绍 Xcode 范畴内的测试概念和自动化测试实践,不会讨论如何编写高质量的测试用例。文中涉及到 Xcode 构建的许多概念,大部分已在前两篇文章中有所说明,建议先大致浏览一遍。
在开始测试之前,有必要简单介绍一下 Xcode 中测试相关的概念,这对于编写自动化测试命令非常重要。因为文中会反复提到这些概念,使用时也必须清楚这些概念之间的关系。
Test Bundle,专门用于测试的 Target。Xcode 13.1 新建项目时勾选 Include Tests 会自动创建单元测试包和 UI 测试包。测试包内组织了多个测试用例。
Test Case,专门用于测试的类 Class,继承自 XCTestCase。在测试用例内导入需要测试的头文件,设置初始环境,并编写多个测试方法。
Test Method,专门用于测试的方法,必须是实例方法且方法名必须以 test 开头且没有参数和返回值,否则不会被 Xcode 识别为测试方法,也就不会被自动调用。
Unit Test,是一种测试维度,主要测试代码逻辑,粒度较细,依赖良好的架构设计并编写可测试的代码(Testable Code)。在测试方法中调用需要测试的代码,并用 XCTAssert 及相关方法来判断结果。
UI Test,在较粗维度上进行测试,模拟用户的操作。Xcode 用 UI Test Recorder 来记录操作序列,自动将序列以代码的形式插入到测试方法中,运行测试方法就是把序列“重放”一次,以观察同样的操作在不同设备环境上的表现。UI 测试可以在需要的位置进行截屏,保留现场。
Test Plan,一个以 .xctestplan 为扩展名的 JSON 格式文件,组合了测试包和配置(Configuration),可以分别设置测试包、测试用例、测试方法是否启用以决定是否参与测试,配置包含一个默认配置和多个自定义配置,大部分配置项来自于 Scheme。自定义配置未指定的配置项由默认配置的对应配置项决定。
在 Scheme Manager 中,可以将 Scheme 转为测试计划,也可以在 Xcode -> Product -> Test Plan 创建新的测试计划。对于大型项目,建议将 Scheme 转为测试计划,因为测试计划包含更多配置项,比如启用测试超时、重复测试等,而且 JSON 文件比 Scheme 更易于版本管理。
必须要为测试计划创建至少一个自定义配置,否则测试计划内的任何测试都将无法运行。在将 Scheme 转为测试计划时,Xcode 帮我们自动完成了这个操作。你也可以添加额外的自定义配置。
Test Reports,列出了每个测试用例的测试结果:执行步骤、耗时、截屏、日志等。
Code Coverage,显示测试用例覆盖了多少代码,Xcode 提供了可视化界面,可据此来完善测试用例。启用代码覆盖率并执行测试后,代码编辑器右边缘就会显示某行被测试的次数。
了解完测试相关的概念,结合 探究 Xcode 命令行用法一:Xcode 构建必备认知 一文对命令行的认知,就可以对 xcodebuild 测试命令进行分析了。在 Terminal 中执行man xcodebuild
提取测试相关的选项,对每个选项和操作的用法说明如下:
-disable-concurrent-destination-testing
禁止在多个环境中并行测试。如果指定了多个环境,一个完成后再开始下一个。
-maximum-concurrent-test-device-destinations NUMBER
测试时如果指定了多个真机环境,同一时间最多在 NUMBER 个真机上执行。和-disable-concurrent-destination-testing
互斥。如果未明确指定 NUMBER,理论上没有上限。
-maximum-concurrent-test-simulator-destinations NUMBER
测试时如果指定了多个模拟器环境,同一时间最多在 NUMBER 个模拟器上执行。和-disable-concurrent-destination-testing
互斥。如果未明确指定 NUMBER,默认为 4。
-parallel-testing-enabled YES|NO
是否启用平行测试。平行测试将测试用例(类)分发到不同的运行器(Runner)或进程中执行,以提高测试效率。单元测试的运行器通常是应用的一个实例,UI 测试的运行器是 Xcode 创建的一个自定义应用。
-parallel-testing-worker-count NUMBER
执行平行测试的运行器数量,会覆盖-maximum-parallel-testing-workers NUMBER
指定的数量。
-maximum-parallel-testing-workers NUMBER
执行平行测试的运行器的最大数量。
-parallelize-tests-among-destinations
将平行测试分发到多环境中执行。如果启用了平行测试并指定在多个环境中测试,那么测试用例会被分发到多环境中执行,而不会将一套完整的测试在每一个环境中执行。
-showTestPlans
显示 Scheme 关联的测试计划,需要和 -scheme 搭配使用。
-testPlan
执行测试时指定测试计划,只传测试计划文件名,不传 .xctestplan 后缀。需要和 -scheme 搭配使用。
-only-test-configuration
只测试指定的配置,参数为自定义配置名称,区分大小写。注意这是测试计划的配置,不是 Target 的 BuildSettings 的变体。
-skip-test-configuration
跳过对指定自定义配置的测试。
-test-timeouts-enabled YES|NO
是否启用测试超时。
-default-test-execution-time-allowance SECONDS
一个测试方法的默认超时时间,单位秒,每 60 秒向上取整,即:小于 60 秒按 60 秒算,大于 60 秒但小于 120 秒按 120 秒算,以此类推,例如:SECONDS 等于 59,实际是 60;SECONDS 等于 61,实际是 120 。需要先启用测试超时:-test-timeouts-enabled YES
。等同于 XCTestCase 实例的 executionTimeAllowance 的作用,另外测试计划中也可以配置默认超时时间。优先级从高到低依次是:
-maximum-test-execution-time-allowance SECONDS
一个测试方法的最大超时时间,单位秒,每 60 秒向上取整。需要先启用测试超时:-test-timeouts-enabled YES
。XCTestCase 实例并未提供相关属性或方法去设置,也没有默认最大超时时间,因此优先级从高到低依次是:
一个测试方法的超时时间由默认超时时间和最大超时时间的较小者决定。达到超时时间后,判定测试失败,并生成一个 Spindump 文件:
后续会有文章介绍如何解析 Spindump 文件。关注我,第一时间获取:virusbee - 本文作者
-test-iterations <number>
重复测试<number>
次。
-retry-tests-on-failure
测试失败后再次尝试,直到测试成功或达到总测试次数。总测试次数默认是 3 次,如果指定了-test-iterations <number>
,则总次数为<number>
次。不能和-run-tests-until-failure
一起用。
-run-tests-until-failure
测试成功后再次运行,直到测试失败或达到总测试次数。总测试次数默认是 100 次,如果指定了-test-iterations <number>
,则总次数为<number>
次。不能和-retry-tests-on-failure
一起用。
-test-repetition-relaunch-enabled YES|NO
是否每个测试都在新进程中执行,如果为 NO,所有的测试都将在同一个进程中执行。必须和-test-iterations <number>
、-retry-tests-on-failure
或-run-tests-until-failure
搭配使用。
-testLanguage
指定测试语言,语言遵循 ISO 639-1 标准,例如 nl、hr、ar。
-testRegion
指定测试地区,地区遵循 ISO 3166-1 标准,例如 GN、KE、IT。
-enableCodeCoverage YES|NO
启用代码覆盖率
-only-testing:TEST-IDENTIFIER
只测试 TEST-IDENTIFIER 指定的测试,关于 TEST-IDENTIFIER 的详细说明,参见 TN2339: 使用 Xcode 命令行构建的常见问题 的如何利用命令行实施单元测试?
-skip-testing:TEST-IDENTIFIER
跳过 TEST-IDENTIFIER 指定的测试。
test
执行测试。
build-for-testing
生成 .xctestrun 文件,作为test-without-building
的参数。
test-without-building
利用build-for-testing
生成的 .xctestrun 文件进行测试,通过-xctestrun
选项来指定。
关于测试操作的详细用法,参见 TN2339: 使用 Xcode 命令行构建的常见问题 的如何利用命令行实施单元测试? 和 如何利用命令行实现 Xcode 中的 Build For Testing 和 Test Without Building 功能?
项目概况
一款 iOS 应用,最低支持 iOS 9.0;应用发行到香港、日本、韩国 3 个地区;支持简体中文、繁体中文、日语、韩语、英语 5 种语言;已有若干单元测试和 UI 测试用例,部分测试用例已整合为测试计划。
项目清单
项目路径:App.xcodeproj
Targets:App(主应用)、AppTests(单元测试包)、AppUITests(UI 测试包)
Scheme:App(配置测试计划)、AppTests(配置单元测试包和UI 测试包)
发行地区:香港 HK、日本 JP、韩国 KR
支持语言:简体中文 zh-Hans、繁体中文 zh-Hant、日语 ja、韩语 ko、英语 en
测试计划:Core.xctestplan(只有一个默认配置和一个自定义配置,配置项均为默认值)
单元测试包内的用例(类):AccountTests,DataTests,ParseTests,HelperTests(方法 testExample 无需测试)
UI 测试包内的用例(类):LoginTests,ChatTests,PaymentTests
现有设备
类型 | 名称或ID | OS |
---|---|---|
真机 | ID:4cbbc9c59cd4c29dad494141b81b2f64ab45d643 | iOS 15.1 |
真机 | ID:00004032-102044371DF4313A | iOS 14.5.1 |
模拟器 | 名称:iPhone 13 Pro | iOS 15.0 |
模拟器 | 名称:iPhone SE | iOS 13.5 |
模拟器 | 名称:iPad Pro | iOS 12.4 |
测试要求
测试计划需要在每台设备上完整执行 10 次测试,直到失败为止,每个测试限制在 1 分钟内完成,模拟器最多同时开 2 个;其余测试用例采用平行测试,测试失败不停止,最多测试 10 次;输出代码覆盖率、测试报告、日志和失败截图。
根据测试要求,测试计划和其余测试用例分开执行,需要写两条命令。
对于测试计划Core.xctestplan
,用-testPlan Core
来指定;测试计划需搭配 Scheme 使用,而该测试计划配置在 App Scheme 下,即-scheme App
;执行 10 次,即-test-iterations 10
;直到失败为止,即-run-tests-until-failure
;为了确保每次都在新进程中测试,添加-test-repetition-relaunch-enabled YES
;每个测试限制在 1 分钟内完成,需要启用测试超时,即-test-timeouts-enabled YES
,并将超时时间设置为 60 秒,即-maximum-test-execution-time-allowance 60
;上表列出的每台设备都要参与测试,根据各自类型、名称、ID、版本来构造 -destination
参数值,有几台设备就写几个-destination
;模拟器最多同时开 2 个,由于模拟器默认最多同时开 4 个,因此设置-maximum-concurrent-test-simulator-destinations 2
;输出代码覆盖率,即启用代码覆盖率-enableCodeCoverage YES
;为了方便获取代码覆盖率、测试报告等,将测试结果输出到指定的位置result1
,即-resultBundlePath result1
。
完整命令:
$ xcodebuild test \ -project App.xcodeproj \ -scheme App \ -testPlan Core \ -test-iterations 10 \ -run-tests-until-failure \ -test-repetition-relaunch-enabled YES \ -test-timeouts-enabled YES \ -maximum-test-execution-time-allowance 60 \ -destination "platform=iOS,id=4cbbc9c59cd4c29dad494141b81b2f64ab45d643" \ -destination "platform=iOS,id=00004032-102044371DF4313A" \ -destination "platform=iOS Simulator,name=iPhone 13 Pro,OS=15.0" \ -destination "platform=iOS Simulator,name=iPhone SE,OS=13.5" \ -destination "platform=iOS Simulator,name=iPad Pro,OS=12.4" \ -maximum-concurrent-test-simulator-destinations 2 \ -enableCodeCoverage YES \ -quiet \ -resultBundlePath result1
思考一下,这个命令执行完成后,所有测试通过,一个普通的测试方法执行了几次?答案是 50 次。这是一个并行测试,因此测试计划会在每台设备上完整执行 10 次,有 5 台设备参与测试,测试计划只有 1 个自定义配置,即:10次 * 5设备 * 1配置 = 50 次。
对于其余测试用例,单元测试用例属于 AppTests 包,UI 测试用例属于 AppUITests 包,且两个包都配置在 AppTests Scheme 下,因此指定-scheme AppTests
; AppTests 包内的 HelperTests 类的 testExample 方法无需测试,即-skip-testing:AppTests/HelperTests/testExample
;最多测试 10 次,即-test-iterations 10
;测试失败不停止,即-retry-tests-on-failure
;采用平行测试,需先启用平行测试,即-parallel-testing-enabled YES
,再将测试用例分到-destination
中执行,即-parallelize-tests-among-destinations
;其余配置和测试计划保持一致。
完整命令:
$ xcodebuild test \ -project App.xcodeproj \ -scheme AppTests \ -skip-testing:AppTests/HelperTests/testExample \ -test-iterations 10 \ -retry-tests-on-failure \ -destination "platform=iOS,id=4cbbc9c59cd4c29dad494141b81b2f64ab45d643" \ -destination "platform=iOS,id=00004032-102044371DF4313A" \ -destination "platform=iOS Simulator,name=iPhone 13 Pro,OS=15.0" \ -destination "platform=iOS Simulator,name=iPhone SE,OS=13.5" \ -destination "platform=iOS Simulator,name=iPad Pro,OS=12.4" \ -parallel-testing-enabled YES \ -parallelize-tests-among-destinations \ -enableCodeCoverage YES \ -quiet \ -resultBundlePath result2
思考一下,这个命令执行完成后,所有测试通过,一个普通的测试方法执行了几次?答案是 1 次。虽然最多执行 10 次,但如果第一次就执行成功,就不会再执行了,这是因为 -retry-tests-on-failure 只决定了测试失败的后续操作,而 -run-tests-until-failure 才是决定测试成功的后续操作,此处并未指定它,也无法指定它,两者是相互排斥的。如果第一次就执行成功,导致所有方法只测试了一次,那这个测试不充分,而测试需求就是这样要求,可以反推这个需求提的并不合理。
测试结果保存在-resultBundlePath
指定的路径,不能是已存在的路径。上述测试计划的测试结果是 result1.xcresult,其余测试用例的测试结果是 result2.xcresult。
双击 result1.xcresult 自动在 Xcode 中打开,查看测试结果:
代码覆盖率:
上面的命令还不够完善,无法支持多个地区和语言的测试要求,某些情况还要模拟地理位置,这应该如何处理?
如果测试任务繁重,又没有很多测试设备可用,往往需要将测试任务委托给第三方来完成,但又不能提供源码,这种情况该如何处理?
很多企业自己有持续构建系统,项目构建完成后会自动发邮件给关注者。直接把 .xcresult 文件通过邮件发送,是很不明智的做法,那如何自己解析 .xcresult 文件获取元数据,并生成自定义的测试报告、代码覆盖率界面呢?
限于篇幅,此处不展开说明,后续会有文章提供解决方案,关注作者第一时间获取。
本文概念非常多,实践部分也需要结合概念去写代码一步步验证,如果不太好理解,建议先收藏下来,慢慢消化。
下面是后续文章的计划(标题和顺序可能变动,以实际发布为准):
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。