• app稳定性测试-iOS篇


    稳定性测试:测试应用程序在长时间运行过程中是否存在内存泄漏、崩溃等问题,以确保应用程序具有较高的稳定性和可靠性。

    对于安卓端,官方提供了很好的稳定性测试工具:monkey。 相比较而言,iOS则没有,而且当前网络上似乎也没有很好的第三方工具可以使用,因此只能自己写了。

    我们要开发的iOS稳定性测试程序,应该至少包含以下内容:

    1. 持续随机触发UI事件
    2. 崩溃重启,测试不中断
    3. 日志记录

    首先我们确定以上设想的可行性,然后再制定实施方案。在iOS原生开发语言swift和object-C中提供了可进行单元测试和UI测试的XCTest框架,而同样可进行移动端UI测试的第三方框架还有Appium等,但相比较第三方的开源框架,原生的XCTest框架性能更好且更稳定,因此这里我们选择基于swift语言和XCTest框架来开发。XCTest框架提供了非常全面的启动App和UI操作相关的API接口, 因此1、2两点完全可以实现,当然第三点的日志记录的实现也同样不会有什么问题。接下来就是具体实施了。

    首先,我们创建一个用来执行测试的主类:StabilityTestRunner,然后再编写代码去实现以上三点。

    一、持续随机触发UI事件

    让我们拆分一下,随机触发UI事件,实际上包含两部分:随机UI元素和随机的UI操作。那么:

    随机生成UI元素:

    func randomElement(of types: [ElementType]) -> XCUIElement? {
            var allElement:[XCUIElement] = []
            for type in types {
                if !self.exists{
                    break
                }
                var elements: [XCUIElement]
                if self.alerts.count > 0 {
                    elements = self.alerts.descendants(matching: type).allElementsBoundByIndex
                }else {
                    elements = self.descendants(matching: type).allElementsBoundByIndex
                }
                let filteredElements = elements.filter { element in
                    if !element.exists {
                        return false
                    }
                    if !element.isHittable || !element.isEnabled {
                        return false // Filter out non clickable and blocked elements.
                    }
                    return true
                }
                allElement.append(contentsOf: filteredElements)
            }
            
            return allElement.randomElement()
        }
    

    随机生成UI操作:

    /**
         Random execution of the given UI operation.
         - parameter element: Page Elements.
         - parameter actions: Dictionary objects containing different UI operations.
         */
        private func performRandomAction(on element: XCUIElement, actions: [String: (XCUIElement) -> ()]) {
            let keys = Array(actions.keys)
            let randomIndex = Int.random(in: 0..<keys.count)
            let randomKey = keys[randomIndex]
            let action = actions[randomKey]
            
            if action == nil {
                return
            }
            
            if !element.exists {
                return
            }
            
            if !element.isHittable {
                return
            }
            Utils.log("step\(currentStep): \(randomKey) \(element.description)")
            action!(element)
        }
    

    二、持续测试和崩溃重启

    while !isTestingComplete{
                // Randomly select page elements.
                let element = app.randomElement(of: elementType)
                if element != nil {
                    currentStep += 1
                    takeScreenshot(element: element!)
                    performRandomAction(on: element!, actions: actions) // Perform random UI operations.
                    XCTWaiter().wait(for: [XCTNSPredicateExpectation(predicate: NSPredicate(format: "self == %d", XCUIApplication.State.runningForeground.rawValue), object: app)], timeout: stepInterval)
                    if app.state != .runningForeground {
                        if app.state == .notRunning || app.state == .unknown {
                            Utils.saveImagesToFiles(images: screenshotData)
                            Utils.saveImagesToFiles(images: screenshotOfElementData, name: "screenshot_element")
                            Utils.log("The app crashed. The screenshot before the crash has been saved in the screenshot folder.")
                        }
                        app.activate()
                        
                    }
                }
            }
    

    三、日志记录

    记录截图并标记UI元素:

    private func takeScreenshot(element: XCUIElement) {
            let screenshot = app.windows.firstMatch.screenshot().image
            if screenshotData.count == 3 {
                let minKey = screenshotData.keys.sorted().first!
                screenshotData.removeValue(forKey: minKey)
            }
            let screenshotWithRect = Utils.drawRectOnImage(image: screenshot, rect: element.frame)
            screenshotData[currentStep] = screenshotWithRect.pngData()
            let screenshotOfElement = element.screenshot().pngRepresentation
            if screenshotOfElementData.count == 3 {
                let minKey = screenshotOfElementData.keys.sorted().first!
                screenshotOfElementData.removeValue(forKey: minKey)
            }
            screenshotOfElementData[currentStep] = screenshotOfElement
        }
    

    通过文本日志记录测试执行过程:

    static func log(_ message: String) {
            print(message)
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
            let dateString = dateFormatter.string(from: Date())
            let fileManager = FileManager.default
            do {
                try fileManager.createDirectory(atPath: logSavingPath, withIntermediateDirectories: true, attributes: nil)
            } catch {
                print("Error creating images directory: \(error)")
            }
            var fileURL: URL
            if #available(iOS 16.0, *) {
                fileURL = URL.init(filePath: logSavingPath).appendingPathComponent("log.txt")
            } else {
                fileURL = URL.init(fileURLWithPath: logSavingPath).appendingPathComponent("log.txt")
            }
            do {
                try "\(dateString) \(message)".appendLineToURL(fileURL: fileURL)
            } catch {
                print("Error writing to log file: \(error)")
            }
    

    日志导出:

    // To add the log files to the test results file, you can view it on your Mac. The test results file path: /User/Library/Developer/Xcode/DerivedData/AppStability-*/Logs.
            let zipFile = "\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/Logs.zip"
            let attachment = XCTAttachment(contentsOfFile: URL(fileURLWithPath: zipFile))
            attachment.name = "Logs"
            attachment.lifetime = .keepAlways
            // Add the "Logs.zip" file to the end of test result file.
            add(attachment)
            Utils.log("The logs for test steps has been added to the end of test result file at /User/Library/Developer/Xcode/DerivedData/AppStability-*/Logs")
    

    注:以上代码只是主体实现,了解相关细节可通过GitHubGitee查阅完整代码。

    总结

    总的来说实现起来并不是很困难,当然从程序使用角度而言,用户可自定义随机UI事件的UI元素范围和UI操作的范围以及测试执行的时长和时间间隔,因此需要对ios应用程序和Xcode的使用以及iOS UI事件有一定的了解,具体使用可查看完整工程中的示例。

  • 相关阅读:
    C语言指针详解(3)———指针题目,你确定你学会指针了?进来看看吧!(几十个指针小题+超详解)
    unet医学肺部ct图分割简单记录
    无用自动化测试?
    听,引擎的声音「GitHub 热点速览 v.22.33」
    传统加密技术(恺撒+仿射)
    测试电脑GPU性能代码
    国企云计算厂商增长迅猛,但私企云下滑
    分享一个学英语的网站
    【Linux】基本指令合集
    详解GPU、CPU差异
  • 原文地址:https://www.cnblogs.com/wang-wang-blog/p/17292697.html