【问题标题】:How to speed up UI test cases in Xcode?如何加快 Xcode 中的 UI 测试用例?
【发布时间】:2016-09-13 22:06:33
【问题描述】:

Xcode 7 开始,我们有了一个很好的 UI 测试 API。 主要是我很满意。唯一担心的是速度。

一开始,一个普通的 UI 测试用例(大约 15 个动作)运行了大约 25 秒。然后我完全嘲笑网络。现在需要 20 秒。考虑到时间仅由动画和启动时间(1 秒甚至更少)占用这一事实,我认为必须有一种方法可以加快速度。

【问题讨论】:

    标签: ios objective-c xcode7 xctest uitest


    【解决方案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()
        }
    }
    

    blog post 中有一些更方便的提示。

    【讨论】:

    • 感谢您的回答。有用!为了稍微改进一下:是否可以从 UI 测试过程中提高动画速度?
    • 很遗憾没有。 ui 测试过程旨在与您的应用完全分开(并且仅通过可访问性和启动参数进行交互)。
    • @ArtemStepanenko 您可以使用SBTUITestTunnel 来提高 UI 测试过程中的动画速度。我们开发了这个库来实现应用程序和测试目标之间的相互通信。
    • @马克嗨!你把这个放在哪里?在您的 AppDelegate 中,还是在启动应用程序之前的 UI 测试中?谢谢。
    • 只是一个任意值。 100 的速度足以满足我的需要。
    【解决方案2】:

    另一种可能性是完全禁用动画:

    [UIView setAnimationsEnabled:NO];
    

    斯威夫特 3:

    UIView.setAnimationsEnabled(false)
    

    【讨论】:

    • 这太棒了!非常感谢你的分享。我的测试套件现在以光速运行。
    • 您不应该完全禁用动画,因为您可能无法捕捉到一些与动画相关的错误。查看此great blog 帖子了解更多信息。
    【解决方案3】:

    根据@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()
    

    【讨论】:

      【解决方案4】:

      在 didFinishLaunch 中添加

      [UIApplication sharedApplication].keyWindow.layer.speed = 2;
      

      默认值为1,设为2使其速度加倍。

      【讨论】:

      • 您能解释一下为什么您的解决方案比其他解决方案更好吗?
      【解决方案5】:

      我想在快照测试期间禁用所有动画。我能够通过禁用 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
      }
      

      【讨论】:

        【解决方案6】:

        并行运行它们。

        如果您只有 1 台构建机器,您可以使用 Bluepill:https://github.com/linkedin/bluepill

        如果您有多台机器,可以使用 Emcee:https://github.com/avito-tech/Emcee(也适用于单机设置)

        我们有 50 小时的 UI 测试,Emcee 允许我们在 1 小时内运行它们。有几个技巧可以让 UI 测试更快,但是如果你不并行运行它们就毫无意义。例如。你不能让你的 25 秒测试在 0.5 秒内运行。


        技巧:

        1. 在不重新安装的情况下运行 XCUIApplication:
        XCUIApplication(
            privateWithPath: nil,
            bundleID: "your.bundle.id"
        )
        

        注意:每次启动 XCUI 测试运行程序时,您应至少使用 XCUIApplication().launch() 启动应用程序以安装应用程序。这需要使用私有 API。

        1. 您可以将某些内容移动到后台线程。例如
        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 来说会更容易。我们想实现这个,但我们没有,所以这只是一个想法。

        1. 切换到 EarlGrey 并进行持续几秒钟的测试(但它们不会是黑盒)。

        2. 如果您有深层链接,请通过深层链接打开屏幕。

        3. 使用大量私有 API 和其他技巧。例如:您可以调用一些私有 API,而不是通过 UI 进入“设置”应用然后重置隐私设置。

        4. 永远不要使用长时间的睡眠。使用轮询。

        5. 加快动画速度。不要禁用它们!

        6. 将一些标志从 UI 测试传递到应用程序以跳过初始警报/教程/弹出窗口。可以节省很多时间。确保至少进行 1 项测试来检查这些警报是否有效。

        【讨论】:

        • 嗨,从 2020 年开始,这是一个很好的答案,我开始设置我的 bluepill 设置,但是知道 Xcode 现在原生支持并行测试,不需要第 3 方,这可以节省我一些时间工具,至少 1 台机器设置。
        【解决方案7】:

        我将 UITests 时间减少了 30%,请按照所有步骤操作:

        运行应用时,添加参数:

        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)
        

        【讨论】:

        • 感谢分享!我确信这需要一些时间和精力。如果您首先阅读其他答案以确保您不会重复其他人已经说过的话,这对每个人都会更加有用。干杯。干得好!
        【解决方案8】:

        请注意,keyWindow 自 iOS 13 起已弃用。具体而言, AppDelegate 中的 UIApplication.sharedApplication.keyWindowself.window.keyWindow 都返回 nil。 This answer 说循环通过UIApplication.shared.windows 并找到isKeyWindow 为true 的那个,但在我使用ios14 的测试中,即使是我唯一的窗口也返回false。最后,设置self.window.layer.speed 对我有用。

        【讨论】:

          猜你喜欢
          • 2015-10-14
          • 2015-10-08
          • 2015-09-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-12-13
          • 2018-06-01
          • 1970-01-01
          相关资源
          最近更新 更多