app稳定性测试-iOS篇

📝 面试求职: 「面试试题小程序」 ,内容涵盖 测试基础、Linux操作系统、MySQL数据库、Web功能测试、接口测试、APPium移动端测试、Python知识、Selenium自动化测试相关、性能测试、性能测试、计算机网络知识、Jmeter、HR面试,命中率杠杠的。(大家刷起来…)

📝 职场经验干货:

软件测试工程师简历上如何编写个人信息(一周8个面试)

软件测试工程师简历上如何编写专业技能(一周8个面试)

软件测试工程师简历上如何编写项目经验(一周8个面试)

软件测试工程师简历上如何编写个人荣誉(一周8个面试)

软件测试行情分享(这些都不了解就别贸然冲了.)

软件测试面试重点,搞清楚这些轻松拿到年薪30W+

软件测试面试刷题小程序免费使用(永久使用)


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

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

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

  • 持续随机触发UI事件

  • 崩溃重启,测试不中断

  • 日志记录

首先我们确定以上设想的可行性,然后再制定实施方案。在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")

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

总结

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

最后: 下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

### iOS App 稳定性测试的方法与工具 #### 方法 1. **Monkey 测试** 在 Android 平台上,Monkey 是一种常用的稳定性测试工具。尽管 iOS 没有直接对应的 Monkey 工具,但可以通过类似的方式实现随机事件模拟。例如,使用 XCTest 或其他自动化测试框架编写脚本,模拟用户操作并进行长时间运行测试[^4]。 2. **模型驱动测试** 使用基于模型的测试工具可以更智能地发现应用中的潜在问题。Fastbot_iOS 是一款字节跳动开源的工具,能够通过建模 GUI 转换来发现稳定性问题。它支持灵活的配置和易于集成的特性,适用于 iOS 应用的稳定性测试[^1]。 3. **长时间运行测试** 为了验证应用在长时间运行下的稳定性,可以设置自动化脚本反复执行关键功能测试。确保设备屏幕保持常亮状态,并监控应用的内存占用、CPU 使用率等指标。如果发现异常,需检查崩溃日志以定位问题[^2]。 #### 工具 1. **XCTest** XCTest 是 Apple 提供的官方测试框架,支持单元测试和 UI 测试。通过编写 UI 测试脚本,可以模拟用户交互并检测应用的稳定性。以下是一个简单的 XCTest 示例代码: ```swift func testExample() throws { let app = XCUIApplication() app.launch() for _ in 0..<1000 { app.buttons["exampleButton"].tap() } } ``` 2. **Fastbot_iOS** Fastbot_iOS 是一款高效的模型驱动测试工具,专为 iOS 应用设计。它能够自动探索应用的界面元素,并生成测试路径以发现潜在的稳定性问题。安装和使用 Fastbot_iOS 需要 XCode 和 CocoaPods 的支持[^3]。 3. **Appium** Appium 是一个跨平台的自动化测试工具,支持 iOS 和 Android 应用的测试。通过编写 Python、Java 等语言的脚本,可以实现对应用的长时间稳定性测试。以下是一个使用 Appium 的 Python 示例代码: ```python from appium import webdriver desired_caps = { 'platformName': 'iOS', 'platformVersion': '15.0', 'deviceName': 'iPhone 12', 'app': '/path/to/app' } driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps) for _ in range(1000): driver.find_element_by_accessibility_id("exampleButton").click() driver.quit() ``` 4. **EarlGrey** EarlGrey 是 Google 开发的一款 iOS 自动化测试工具,专注于提供精确的 UI 测试能力。它可以用于验证应用在各种复杂场景下的表现,从而提高稳定性。 #### 注意事项 - 在进行稳定性测试之前,必须确保设备已启用开发者模式并正确连接到测试环境。 - 如果测试过程中发现崩溃或其他异常,应立即查看日志信息(如通过 XCode 的调试工具),以便快速定位问题原因[^2]。 - 对于无线调试,需要先通过 USB 连接初始化设备,然后切换到无线模式[^4]。 ```swift // 示例代码:使用 XCTest 进行长按测试 func testLongPress() throws { let app = XCUIApplication() app.launch() let element = app.buttons["exampleButton"] let coordinate = element.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) coordinate.press(forDuration: 5.0) } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值