编程知识 cdmana.com

iOS 稳定性:编写会“失败”的测试

Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

扫码关注添加客服

进Python社群▲

扫码关注添加客服

进Java社群

作者:NathanSun, iOS开发者,目前就职于字节跳动音乐团队

Sessions: https://developer.apple.com/videos/play/wwdc2020/10091/

这个 session 的标题很有意思,编写会“失败”的测试。一般情况下,工程师都希望写完测试代码,一路“绿灯”全部通过,然后窃喜于自己写的代码多么牛逼。但是真正好的测试代码反而是能够抓住潜在bug的测试代码,也就是会让测试“失败”的代码。请注意这个session的内容主要是讲UI测试,但是同样适用于单元测试。

测试用例可以是在本地 Xcode 跑,也可以在 CI 机器上跑。如果在 CI 上跑的话,测试结果会在 result bundle 里面,然后我们可以根据 test bundle 打印的信息分析测试结果。好的测试代码,应该能帮助快读定位问题,而且更加健壮,这个 session 会告诉大家如何做到以上这两点。我们知道,测试一般分为三个步骤,set up, test, tear down。其中第二步 test 又可以分为两个子步骤,行为(actions)和断言(assertions),这个 session 就是以此为框架具体阐述如何编写好的测试代码。

先谈第一步 Set up

Xcode 11.4 增加了一个新的 API setUpWithError() throws。建议之前用 setUp() 的同学都迁移到这个新的 API,如果 setUp 过程发生错误,这个 API 可以抛出异常并提前终止测试。

在这个方法里一般需要重置 app 状态,比如上次测试权限打开了应用权限,这里可以 reset 应用权限。一般建议设置 continueAfterFailure = false,  这样在失败后立即停止继续测试,确保遇到第一个错误后立即停止,避免多个错误混在一起对排查问题造成困难。最后这个方法需要运行 app.launch() 启动 app,启动 app 之前也可以添加启动参数或者环境变量,例如 app.launchArguments.append('"-recipes-tests")。加启动参数或者环境变量的主要目的是加速你的测试过程,比如你需要跳过两步验证,又或者你的 app 有四个 tab,而你需要直接跳到第四个 tab 进行相关场景测试。这样可以快速到达你的测试场景,也可以避免在其他前置场景就已经测试失败影响你的测试。

第二步 Test

测试无非就是触发某个行为,并通过断言判断这个行为带来预期的结果。

行为(Actions)

测试方法的命名很重要,一般你要想好你的测试目标是什么,然后就用这个目的来命名。比如你是要测试一个食谱 APP  里面的 Ingredients 列表的正确性,那么你可以将测试方法命名为 testIngredientsListAccuracy。下面是这个测试方法的具体内容,即测试 APP 里面的一个奶昔列表,选中一个品类,就可以看到这个品类对应的原料详情页。

在实际编写测试行为代码的过程中,总结下来有下面的小技巧:

  1. 同一个 UI 的名字经常变怎么办?可以用enum来解决,把字符串转成 enum 的形式来表达,这样如果 Label 的名字变了,只需要把 enum 对应的 string 值直接修改即可。

  2. 把多个测试中出现的重复代码包装成 helper function。

  3. 用面向对象的方法去组织测试代码,把测试代码写成易读的形式,比如 app.smoothieList().select(smoothie: .berryblue) 一眼就能看出是想要做的事情是从列表中点击一个元素。

  4. 随着测试代码越来越庞大,可以把一些测试代码用产品代码一样的方式管理起来,比如包装成 framework 或者是 Swift package,这样可以在多个项目之间共享测试代码。

断言(Aassertions)

断言就是判断结果是否符合预期,使用断言时需要注意以下几点。

  1. 不要忽视 XCTAssert 里面的可选变量 message 的作用。测试更多的时候是跑在 CI 机器上,所以有越多的上下文信息肯定对定位问题越有帮助。比如 XCTAsssertEqual(count, expectedCount),这是一个不太好的写法,完全缺失了上下文信息,比较好的写法是如下图所示,把上下文信息补充在 message 里面。很多项目会用自动化工具去收集汇总这些测试结果,所以一般建议不要在上下文信息里面里面包含特定文件名路径、时间戳等,而是只留下一些通用信息,这样可以确保同样的错误可以聚合在一起。

  1. 根据实际情况选用最恰当的 Assert 语句,能起到事半功倍的作用。可以考虑使用在 Xcode 12.0 中,新加入的 XCTIssue。关于这个API的详细信息,请参考这个 session[1]

  1. 处理异步的测试任务,比如点击一个button获取网络数据,等待网络数据返回并检查结果。有几种做法:第一种就是 sleep 一段时间盲等待。另一种办法是用 waitForExistence(timeout: 5),这样会用轮询的办法去检查结果,会更加高效。推荐使用第二种办法。

  2. 解 optional 最好用 XCTUnwrap。举个例子,favorites 是一个 optional,可以用 try XCTUnwrap(favorites, "favorites is nil, so there is nothing to count"),这样如果 favorites 是一个 nil 值,会抛出异常,测试程序不会 crash 而且 tear down 能继续执行。

  3. 如果是使用公共库中的测试代码,可以从公共库中抛出异常,并打印出足够多的信息,方便检查问题。

  4. 多使用 XCTContext.runActivity(named:, block:) 描述测试的上下文信息,也可以在 context 里面加上 XCTAttachment,比如文件、数据、图片信息等。

  5. 善用 XCTSkipUnless, XCTSkipIf 跳过一些测试,  这是在 Xcode 11.4 开始提供的 API。这样,测试结果报告中会标记提醒哪些测试过程是暂时跳过的,将来你就不会忘记还需要补充这些功能。一般用在以下场景中。

  • 跳过不相关的平台或者系统版本

  • 跳过还没有来得及实现的新测试代码

  • 暂时无法修复的测试 bug

最后来看一下 tear down

关于 tear down,有三点建议。

  1. 使用 XCode 11.4 提供的新 API,func tearDownWithError() throws。

  2. 在 tear down 方法里加上一些额外的日志,也可以提供一些简单的失败结果分析。

  3. 可以重置 setup 过程中的一些设置,避免对下次测试造成干扰。

参考资料

[1]

session: https://developer.apple.com/videos/play/wwdc2020/10687/

程序员专栏 扫码关注填加客服 长按识别下方二维码进群

近期精彩内容推荐:  

 又一个程序员,被抓捕!(真实事件)

 程序员有个可爱女朋友是种什么体验?

 “12306”的架构到底有多牛逼?

 csv文件读写乱码问题的一个简单解决方法


在看点这里好文分享给更多人↓↓

版权声明
本文为[osc_p5er4r6g]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4297414/blog/4837349

Scroll to Top