app穩(wěn)定性測(cè)試-iOS篇

穩(wěn)定性測(cè)試:測(cè)試應(yīng)用程序在長(zhǎng)時(shí)間運(yùn)行過(guò)程中是否存在內(nèi)存泄漏访诱、崩潰等問(wèn)題拒贱,以確保應(yīng)用程序具有較高的穩(wěn)定性和可靠性铐刘。

對(duì)于安卓端,官方提供了很好的穩(wěn)定性測(cè)試工具:monkey镰惦。 相比較而言迷守,iOS則沒(méi)有,而且當(dāng)前網(wǎng)絡(luò)上似乎也沒(méi)有很好的第三方工具可以使用旺入,因此只能自己寫(xiě)了兑凿。

我們要開(kāi)發(fā)的iOS穩(wěn)定性測(cè)試程序,應(yīng)該至少包含以下內(nèi)容:

  1. 持續(xù)隨機(jī)觸發(fā)UI事件
  2. 崩潰重啟茵瘾,測(cè)試不中斷
  3. 日志記錄

首先我們確定以上設(shè)想的可行性礼华,然后再制定實(shí)施方案。在iOS原生開(kāi)發(fā)語(yǔ)言swift和object-C中提供了可進(jìn)行單元測(cè)試和UI測(cè)試的XCTest框架拗秘,而同樣可進(jìn)行移動(dòng)端UI測(cè)試的第三方框架還有Appium等圣絮,但相比較第三方的開(kāi)源框架,原生的XCTest框架性能更好且更穩(wěn)定聘殖,因此這里我們選擇基于swift語(yǔ)言和XCTest框架來(lái)開(kāi)發(fā)晨雳。XCTest框架提供了非常全面的啟動(dòng)App和UI操作相關(guān)的API接口, 因此1奸腺、2兩點(diǎn)完全可以實(shí)現(xiàn)餐禁,當(dāng)然第三點(diǎn)的日志記錄的實(shí)現(xiàn)也同樣不會(huì)有什么問(wèn)題。接下來(lái)就是具體實(shí)施了突照。

首先帮非,我們創(chuàng)建一個(gè)用來(lái)執(zhí)行測(cè)試的主類:StabilityTestRunner,然后再編寫(xiě)代碼去實(shí)現(xiàn)以上三點(diǎn)。

一末盔、持續(xù)隨機(jī)觸發(fā)UI事件

讓我們拆分一下筑舅,隨機(jī)觸發(fā)UI事件,實(shí)際上包含兩部分:隨機(jī)UI元素和隨機(jī)的UI操作陨舱。那么:

隨機(jī)生成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()
    }

隨機(jī)生成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)
    }

二翠拣、持續(xù)測(cè)試和崩潰重啟

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()
                    
                }
            }
        }

三、日志記錄

記錄截圖并標(biāo)記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
    }

通過(guò)文本日志記錄測(cè)試執(zhí)行過(guò)程:

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)")
        }

日志導(dǎo)出:

// 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")

注:以上代碼只是主體實(shí)現(xiàn)游盲,了解相關(guān)細(xì)節(jié)可通過(guò)GitHubGitee查閱完整代碼误墓。

總結(jié)

總的來(lái)說(shuō)實(shí)現(xiàn)起來(lái)并不是很困難,當(dāng)然從程序使用角度而言益缎,用戶可自定義隨機(jī)UI事件的UI元素范圍和UI操作的范圍以及測(cè)試執(zhí)行的時(shí)長(zhǎng)和時(shí)間間隔谜慌,因此需要對(duì)ios應(yīng)用程序和Xcode的使用以及iOS UI事件有一定的了解,具體使用可查看完整工程中的示例莺奔。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末欣范,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子令哟,更是在濱河造成了極大的恐慌恼琼,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件励饵,死亡現(xiàn)場(chǎng)離奇詭異驳癌,居然都是意外死亡滑燃,警方通過(guò)查閱死者的電腦和手機(jī)役听,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)表窘,“玉大人典予,你說(shuō)我怎么就攤上這事±盅希” “怎么了瘤袖?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)昂验。 經(jīng)常有香客問(wèn)我捂敌,道長(zhǎng),這世上最難降的妖魔是什么既琴? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任占婉,我火速辦了婚禮,結(jié)果婚禮上甫恩,老公的妹妹穿的比我還像新娘逆济。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布奖慌。 她就那樣靜靜地躺著抛虫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪简僧。 梳的紋絲不亂的頭發(fā)上建椰,一...
    開(kāi)封第一講書(shū)人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音岛马,去河邊找鬼广凸。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蛛枚,可吹牛的內(nèi)容都是我干的谅海。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蹦浦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼扭吁!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起盲镶,我...
    開(kāi)封第一講書(shū)人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤侥袜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后溉贿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體枫吧,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年宇色,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了九杂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宣蠕,死狀恐怖例隆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情抢蚀,我是刑警寧澤镀层,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站皿曲,受9級(jí)特大地震影響唱逢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜屋休,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一坞古、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧博投,春花似錦绸贡、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捧挺。三九已至,卻和暖如春尿瞭,著一層夾襖步出監(jiān)牢的瞬間闽烙,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工声搁, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留黑竞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓疏旨,卻偏偏與公主長(zhǎng)得像很魂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子檐涝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容