Retryable 0.6.0

Retryable 0.6.0

Kane Cheshire 维护。



Retryable 0.6.0

Retryable

CI Status Version License Platform

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记录失败,但同时指令正在运行XCTestCaseXCTestCaseRun忽略此失败。这一步骤很重要,因为XCTestCase需要失败以阻止测试继续运行,但XCTestCaseRun需要忽略失败,这样就不会将整个测试执行标记为失败。
  • 最后,Retryable观察XCTestSuite的结束,然后在新的XCTestSuite中重新运行检测到的失败测试。

注意:除了检测失败是否由于 flakes 引起之外,Retryable 还会检查测试函数是否已经重试了最大次数。默认情况下,这个次数是 1 次。

妙知一处

由于 Retryable 会截获调用以记录失败,因此标记为 flaky 而测试失败的测试,如果在等待重试的队列中,将显示为通过。这有点令人困惑,但不可避免,因为如果允许 XCTest 将 flakes 标记为失败,将导致整个运行失败,从而使 flaky 重试变得毫无意义。

Example

安装

Retryable 目前可通过 Cocoapods 使用。当 Xcode 11 发布时,Retryable 将仅通过 Swift 包管理器可用。

作者

kane.codes, @kanecheshire

许可

Retryable 在 MIT 许可下提供。有关更多信息,请参阅 LICENSE 文件。