Retryable
Retryable
是一个小的库,可以使得您的 iOS UI/自动化测试在测试失败时能够重试。
优点
与其他某些选项相比,当自动化测试失败时重新运行测试,Retryable
会仅重新运行失败的个别测试函数,而不是再次运行整个测试套件,这可能会非常耗时。
关键的是,Retryable
期望您标记测试的特定部分为失效,这样在测试函数过程中的任何其他失败都不会自动重试,并将正常失败。
除了上述功能,Retryable
还非常适合并行自动化测试,并将重试测试添加到xcresult
包中的JSON文件中,以便您可以在CI上跟踪无效和重试。
注意:
Retryable
不与通过Fastlane的scan
运行的scan
并行UI测试一起工作。更多信息 在此。并行测试目前不受影响。如果您的测试还没有并行,强烈考虑进行并行测试!
启用重试
要启用重试,您只需要做两件事:
1: 将您的测试用例从XCTestCase
子类化改为RetryableTestCase
import Retryable
class MyUITests: RetryableTestCase {
}
2: 标记可能会失败的测试用例的部分为不稳定
import Retryable
class MyUITests: RetryableTestCase {
func test_awesomeFeature() {
// ... Your automation code you're always expecting to work ...
flaky(.notFixable(reason: "UserDefaults doesn't always save properly on the iOS 11 simulator", maxRetryCount: 1)) {
// ... Your automation code that sometimes fails because UserDefaults is unreliable
}
// ... Some more of your automation code you're always expecting to work ...
}
}
注意函数的部分被标记为不稳定,以及在标记为不稳定时,您需要确定它是不稳定的还是可修复的。
无论你认为问题是否修复,你都需要提供一个原因。
注意:你只能控制修复不了的错误的重试次数。可修复的错误只会重试一次。
这两个要求有助于防止草率地将所有错误标记为“不稳”,并帮助记录编码库中的问题,以便未来的开发者参考。
Retryable
在将函数作为“不稳”处理时,提供了一个方便的 @autoclosure
参数,因此你可以省略 {}
func test_anotherAwesomeFeature() {
flaky(.fixable(reason: "There's a race condition here!"), XCTAssert(somethingToAssert))
}
而不是
func test_anotherAwesomeFeature() {
flaky(.fixable(reason: "There's a race condition here!")) {
XCTAssert(somethingToAssert)
}
}
在持续集成中发现重试
了解一个测试是否被重试可能很重要,这样你可以跟踪问题的稳定性。Retryable通过创建一个包含所有重试测试的JSON文件来帮助实现这一目标,你可以在您的CI过程中查找并解析此文件。
以下是名为retryable-retries.json
的JSON文件的结构
{
"retries": [
{
"name": "-[MyUITests test_awesomeFeature]",
"maxRetriesAllowed": 2,
"attemptedRetries": 1,
"reason": "UserDefaults doesn't always save properly on the iOS 11 simulator",
"fixable": false
},
{
"name": "-[SomeMoreUITests test_anotherAwesomeFeature]",
"maxRetriesAllowed": 1,
"attemptedRetries": 0,
"reason": "We've got a race condition here",
"fixable": true
}
]
}
注意:Xcode的xcresult bundle是在“Derived Data”中自动生成的,您需要决定如何在您的CI过程中提取这些信息!
一旦检测和解析了该文件,您就可以发送一条Slack消息,比如指示测试已经通过,但上述测试必须重试。
使用Fastlane/Ruby解析此的例子可能是这样的
lane :tests do |options|
scan
path_to_derived_data = lane_context[:SCAN_DERIVED_DATA_PATH]
path_to_json = Dir["#{path_to_derived_data}/**/*.xcresult/retryable-retries.json"].last
if path_to_json != nil
file = File.open(path_to_json, 'rb')
retries = JSON.parse(file.read)
file.close
count_of_retried_tests = retries["retries"].count
# Do something with the count of failures that were retried, like send a Slack message
end
end
内部机制
Retryable
的工作方式不明显,需要了解XCTestCase
如何运行。
对于你想要运行的每个测试函数,XCTest
都会为定义函数的XCTestCase
创建一个新实例。这意味着你可以为每个函数有多个测试实例,每个函数一个实例。XCTest
为每个XCTestCase
初始化一个实际的函数运行的Selector
。
Retryable
做的是同样的事情,检测失败后创建一个带有相同选择器的XCTestCase
来重新运行测试。为了能够重新运行测试,需要以下几点
- 首先,
Retryable
需要通过截取对在XCTestCase
中记录失败的调用来检测失败。检测到失败时,Retryable
检查当前状态是否设置为“不稳”。 - 如果目前不是“不稳”状态,失败将按正常方式记录,但如果它是“不稳”状态,则允许
XCTestCase
记录失败,但同时指令正在运行XCTestCase
的XCTestCaseRun
忽略此失败。这一步骤很重要,因为XCTestCase
需要失败以阻止测试继续运行,但XCTestCaseRun
需要忽略失败,这样就不会将整个测试执行标记为失败。 - 最后,
Retryable
观察XCTestSuite
的结束,然后在新的XCTestSuite
中重新运行检测到的失败测试。
注意:除了检测失败是否由于 flakes 引起之外,Retryable 还会检查测试函数是否已经重试了最大次数。默认情况下,这个次数是 1 次。
妙知一处
由于 Retryable
会截获调用以记录失败,因此标记为 flaky 而测试失败的测试,如果在等待重试的队列中,将显示为通过。这有点令人困惑,但不可避免,因为如果允许 XCTest
将 flakes 标记为失败,将导致整个运行失败,从而使 flaky 重试变得毫无意义。
安装
Retryable
目前可通过 Cocoapods 使用。当 Xcode 11 发布时,Retryable
将仅通过 Swift 包管理器可用。
作者
许可
Retryable 在 MIT 许可下提供。有关更多信息,请参阅 LICENSE 文件。