如何在 Xcode 中加速 UI 个测试用例?
How to speed up UI test cases in Xcode?
自从 Xcode 7 我们有一个很好的 API 用于 UI 测试。
主要是我对它很满意。唯一担心的是速度。
一开始是一个普通的 UI 测试用例(大约 15 个动作)运行 大约 25 秒 。然后我完全嘲笑网络。现在需要 20 秒。考虑到时间仅由动画和启动时间(1 秒或更短)占用,我想,一定有办法加快速度。
当您的 UI 测试 运行 时尝试设置此 属性:
UIApplication.shared.keyWindow?.layer.speed = 100
我是这样设置的:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("UITests") {
UIApplication.shared.keyWindow?.layer.speed = 100
}
}
在我的 UI 测试中:
class MyAppUITests: XCTestCase {
// MARK: - SetUp / TearDown
override func setUp() {
super.setUp()
let app = XCUIApplication()
app.launchArguments = ["UITests"]
app.launch()
}
}
中还有一些更实用的提示
另一种可能性是完全禁用动画:
[UIView setAnimationsEnabled:NO];
Swift 3:
UIView.setAnimationsEnabled(false)
按照@Mark 的回答,Swift 3 版本:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("UITests") {
UIApplication.shared.keyWindow?.layer.speed = 200
}
}
你ui测试文件:
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
let app = XCUIApplication()
app.launchArguments = ["UITests"]
app.launch()
在didFinishLaunch中添加
[UIApplication sharedApplication].keyWindow.layer.speed = 2;
默认值为1,设为2,速度翻倍
我想在快照测试期间禁用所有动画。我能够通过禁用 Core Animation 和 UIView 动画来实现这一点,如下所示。
请注意,因为我的应用程序使用故事板 UIApplication.shared.keyWindow
在启动时为零,所以我通过直接引用 window
属性 来访问 UIWindow。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("SnapshotTests") {
// Disable Core Animations
window?.layer.speed = 0
// Disable UIView animations
UIView.setAnimationsEnabled(false)
}
return true
}
运行他们并行。
编辑:自从我在 2019 年的原始回答以来,这可能已经过时了,因为 Xcode 现在允许在一台机器上的多个模拟器上进行测试:
If you have only 1 build machine, you can use Bluepill: https://github.com/linkedin/bluepill
编辑:但是,我不使用其中任何一个(bluepill / Xcode),所以我会在这个答案中不断提到 bluepill,也许它有一些用途。
如果您有多台机器,可以使用 Emcee:https://github.com/avito-tech/Emcee(它也适用于单台机器设置)
我们有 50 个小时的 UI 测试,Emcee 允许我们在 1 小时内完成 运行。有几个技巧可以使 UI 测试更快,但如果您不 运行 并行测试它们,那将毫无意义。例如。你不能在 0.5 秒内完成 25 秒测试 运行。
技巧:
- 运行 XCUI无需重新安装的应用程序:
XCUIApplication(
privateWithPath: nil,
bundleID: "your.bundle.id"
)
注意:每次启动 XCUI 测试 运行 时,您应该至少使用 XCUIApplication().launch()
启动应用程序一次以安装应用程序。这需要使用 private API.
- 您可以将某些内容移至后台线程。例如
let someDataFromApi = getSomeData() // start request asynchronously and immediately return
launchApp() // this can be few seconds
useInTest(someDataFromApi) // access to data will wait request to finish
你可以让代码看起来没有异步的东西,QA 会更容易。我们想实现这个,但我们没有,所以这只是一个想法。
切换到 EarlGrey 并进行持续几秒钟的测试(但它们不会是黑盒)。
如果您有深层链接,则通过深层链接打开屏幕。
使用大量私人 API 和其他技巧。例如:不是通过 UI 转到设置应用程序然后重置隐私设置,您可以调用一些私人 API.
切勿使用长时间睡眠。使用轮询。
加快动画速度。不要禁用它们! (我们在每个视图上都使用 layer.speed = 100
并且我们在值 10000 时遇到了严重的问题,完整代码:https://pastebin.com/AnsZmzuQ)
将 UI 测试中的一些标志传递给应用以跳过初始 alerts/tutorials/popups。可以节省很多时间。确保至少进行 1 次测试来检查这些警报是否有效。
广告:其中大部分在 https://github.com/avito-tech/Mixbox 中实现。免责声明:我是这个测试框架的主要贡献者。设置起来很繁琐,但如果你不想重用整个框架,至少你可以重用一些代码。
我减少了 30% 的 UITests 时间,遵循所有步骤:
当您 运行 您的应用程序时,添加参数:
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["--Reset"]
app.launch()
}
现在,在您的 AppDelegate 中添加:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setStateForUITesting()
}
static var isUITestingEnabled: Bool {
get {
return ProcessInfo.processInfo.arguments.contains("--Reset")
}
}
private func setStateForUITesting() {
if AppDelegate.isUITestingEnabled {
// If you need reset your app to clear state
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
// To speed up your tests
UIApplication.shared.keyWindow?.layer.speed = 2
UIView.setAnimationsEnabled(false)
}
}
在您的代码中,要验证是否处于测试模式,您可以使用:
if AppDelegate.isUITestingEnabled {
print("Test Mode")
}
此外,为了可以 wait
在元素加载时我创建了这个扩展:
import XCTest
extension XCUIElement {
func tap(wait: Int, test: XCTestCase) {
if !isHittable {
test.expectation(for: NSPredicate(format: "hittable == true"), evaluatedWith: self, handler: nil);
test.waitForExpectations(timeout: TimeInterval(wait), handler: nil)
}
tap()
}
}
这样使用:
app.buttons["start"].tap(wait: 20, test: self)
请注意,keyWindow
自 iOS 13 起已弃用。具体来说,
AppDelegate 中的 UIApplication.sharedApplication.keyWindow
和 self.window.keyWindow
都 return 为零。 说要遍历 UIApplication.shared.windows
并找到 isKeyWindow
为真的那个,但在我使用 ios14 的测试中,即使 return 对我唯一的 window。最后,设置 self.window.layer.speed
对我有用。
自从 Xcode 7 我们有一个很好的 API 用于 UI 测试。 主要是我对它很满意。唯一担心的是速度。
一开始是一个普通的 UI 测试用例(大约 15 个动作)运行 大约 25 秒 。然后我完全嘲笑网络。现在需要 20 秒。考虑到时间仅由动画和启动时间(1 秒或更短)占用,我想,一定有办法加快速度。
当您的 UI 测试 运行 时尝试设置此 属性:
UIApplication.shared.keyWindow?.layer.speed = 100
我是这样设置的:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("UITests") {
UIApplication.shared.keyWindow?.layer.speed = 100
}
}
在我的 UI 测试中:
class MyAppUITests: XCTestCase {
// MARK: - SetUp / TearDown
override func setUp() {
super.setUp()
let app = XCUIApplication()
app.launchArguments = ["UITests"]
app.launch()
}
}
中还有一些更实用的提示
另一种可能性是完全禁用动画:
[UIView setAnimationsEnabled:NO];
Swift 3:
UIView.setAnimationsEnabled(false)
按照@Mark 的回答,Swift 3 版本:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("UITests") {
UIApplication.shared.keyWindow?.layer.speed = 200
}
}
你ui测试文件:
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
let app = XCUIApplication()
app.launchArguments = ["UITests"]
app.launch()
在didFinishLaunch中添加
[UIApplication sharedApplication].keyWindow.layer.speed = 2;
默认值为1,设为2,速度翻倍
我想在快照测试期间禁用所有动画。我能够通过禁用 Core Animation 和 UIView 动画来实现这一点,如下所示。
请注意,因为我的应用程序使用故事板 UIApplication.shared.keyWindow
在启动时为零,所以我通过直接引用 window
属性 来访问 UIWindow。
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
if ProcessInfo.processInfo.arguments.contains("SnapshotTests") {
// Disable Core Animations
window?.layer.speed = 0
// Disable UIView animations
UIView.setAnimationsEnabled(false)
}
return true
}
运行他们并行。
编辑:自从我在 2019 年的原始回答以来,这可能已经过时了,因为 Xcode 现在允许在一台机器上的多个模拟器上进行测试:
If you have only 1 build machine, you can use Bluepill: https://github.com/linkedin/bluepill
编辑:但是,我不使用其中任何一个(bluepill / Xcode),所以我会在这个答案中不断提到 bluepill,也许它有一些用途。
如果您有多台机器,可以使用 Emcee:https://github.com/avito-tech/Emcee(它也适用于单台机器设置)
我们有 50 个小时的 UI 测试,Emcee 允许我们在 1 小时内完成 运行。有几个技巧可以使 UI 测试更快,但如果您不 运行 并行测试它们,那将毫无意义。例如。你不能在 0.5 秒内完成 25 秒测试 运行。
技巧:
- 运行 XCUI无需重新安装的应用程序:
XCUIApplication(
privateWithPath: nil,
bundleID: "your.bundle.id"
)
注意:每次启动 XCUI 测试 运行 时,您应该至少使用 XCUIApplication().launch()
启动应用程序一次以安装应用程序。这需要使用 private API.
- 您可以将某些内容移至后台线程。例如
let someDataFromApi = getSomeData() // start request asynchronously and immediately return
launchApp() // this can be few seconds
useInTest(someDataFromApi) // access to data will wait request to finish
你可以让代码看起来没有异步的东西,QA 会更容易。我们想实现这个,但我们没有,所以这只是一个想法。
切换到 EarlGrey 并进行持续几秒钟的测试(但它们不会是黑盒)。
如果您有深层链接,则通过深层链接打开屏幕。
使用大量私人 API 和其他技巧。例如:不是通过 UI 转到设置应用程序然后重置隐私设置,您可以调用一些私人 API.
切勿使用长时间睡眠。使用轮询。
加快动画速度。不要禁用它们! (我们在每个视图上都使用
layer.speed = 100
并且我们在值 10000 时遇到了严重的问题,完整代码:https://pastebin.com/AnsZmzuQ)将 UI 测试中的一些标志传递给应用以跳过初始 alerts/tutorials/popups。可以节省很多时间。确保至少进行 1 次测试来检查这些警报是否有效。
广告:其中大部分在 https://github.com/avito-tech/Mixbox 中实现。免责声明:我是这个测试框架的主要贡献者。设置起来很繁琐,但如果你不想重用整个框架,至少你可以重用一些代码。
我减少了 30% 的 UITests 时间,遵循所有步骤:
当您 运行 您的应用程序时,添加参数:
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = false
app.launchArguments += ["--Reset"]
app.launch()
}
现在,在您的 AppDelegate 中添加:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
setStateForUITesting()
}
static var isUITestingEnabled: Bool {
get {
return ProcessInfo.processInfo.arguments.contains("--Reset")
}
}
private func setStateForUITesting() {
if AppDelegate.isUITestingEnabled {
// If you need reset your app to clear state
UserDefaults.standard.removePersistentDomain(forName: Bundle.main.bundleIdentifier!)
// To speed up your tests
UIApplication.shared.keyWindow?.layer.speed = 2
UIView.setAnimationsEnabled(false)
}
}
在您的代码中,要验证是否处于测试模式,您可以使用:
if AppDelegate.isUITestingEnabled {
print("Test Mode")
}
此外,为了可以 wait
在元素加载时我创建了这个扩展:
import XCTest
extension XCUIElement {
func tap(wait: Int, test: XCTestCase) {
if !isHittable {
test.expectation(for: NSPredicate(format: "hittable == true"), evaluatedWith: self, handler: nil);
test.waitForExpectations(timeout: TimeInterval(wait), handler: nil)
}
tap()
}
}
这样使用:
app.buttons["start"].tap(wait: 20, test: self)
请注意,keyWindow
自 iOS 13 起已弃用。具体来说,
AppDelegate 中的 UIApplication.sharedApplication.keyWindow
和 self.window.keyWindow
都 return 为零。 UIApplication.shared.windows
并找到 isKeyWindow
为真的那个,但在我使用 ios14 的测试中,即使 return 对我唯一的 window。最后,设置 self.window.layer.speed
对我有用。