基于 KIF 的 iOS UI 自动化测试和持续集成
客戶端 UI 自動化測試是大多數(shù)測試團隊的研究重點,本文介紹貓眼測試團隊在貓眼 iOS 客戶端實踐的基于 KIF 的 UI 自動化測試和持續(xù)集成過程。
一、測試框架的選擇
iOS UI 自動化測試框架有不少,其中 UI Automation 是 Apple 早期提供的 UI 自動化測試解決方法,用 JavaScript 編寫測試腳本,通過標簽和值的可訪問性獲得 UI 元素,來完成相應(yīng)的交互操作。
一些第三方 UI 解決方案以 UI Automation 為基礎(chǔ),對其進行補充和優(yōu)化,包括擴展型 UI Automation 和驅(qū)動型 UI Automation。
- 擴展型 UI Automation 采用 JavaScript 擴展庫方法提高 UI Automation 的易用性,常見的框架有 TuneupJs、ynm3k。
- 驅(qū)動型 UI Automation 在自動化測試底層使用了 UI Automation 庫,通過 TCP 等通信方式驅(qū)動 UI Automation 來完成自動化測試。這種方式下,編輯腳本的語言不再局限于 JavaScript 。常見的框架有 iOSDriver、Appium。
還有一些其他的第三方解決方案,常見的框架類型有私有 API 型和注入編譯型。
- 私有 API 型框架直接使用 Apple 私有 API 對 UI 界面進行操作。常見的框架主要有 KIF。
- 注入編譯型框架在編譯時注入一個 Server 到 App 內(nèi)部,通過 Server 對外通信完成 UI 操作指令的執(zhí)行。常見的框架有 Frank、Calabash。
Xcode 7發(fā)布后,Apple 提供了一種新的 UI 自動化測試解決方法——UI Testing,它基于 XCTest 測試框架,通過控件的可訪問性來定位和獲取控件,并提供了多種 UI 操作 API,使用源碼語言,能方便地進行調(diào)試。 我們在以上分類中挑選具有代表性的自動化框架:UI Automation、Appium、KIF、Frank、UI Testing 進行對比,下表是這幾種測試框架的特點對比:
考慮選擇測試框架的幾種影響因素。首先,使用的語言和框架決定了測試人員的持續(xù)性學(xué)習(xí)成本,iOS 測試人員對 Objective—C 和 XCTest 熟悉和掌握程度高,不需要消耗額外的學(xué)習(xí)成本,人員更替時的接手成本也相對較低;其次,測試框架支持的 UI 操作的豐富性決定了測試用例的覆蓋完整度,使用私有 API 的測試框架支持的 UI 操作較為全面,而同時支持 UIWebView 的測試框架則更占優(yōu)勢;另外,App 程序 UI 變化快,使用開發(fā)效率高、調(diào)試方便的測試框架能使我們在適應(yīng)新 UI 變化、新需求時獲得更小的投入產(chǎn)出比。 綜合以上考慮,KIF 框架已經(jīng)展現(xiàn)了他的優(yōu)勢,并且 KIF 使用 XCTest 框架,使得其測試流程 iOS 程序的單測無異,可完全復(fù)用單測的持續(xù)集成流程,維護持續(xù)集成的成本相對降低;另外,KIF 是一個活躍的開源測試框架,可擴展性好,升級更新快,有活躍社區(qū)來探討和解決使用過程中遇到的問題。鑒于上述優(yōu)勢,我們選擇了 KIF 作為 iOS 的 UI 自動化測試框架。
二、KIF 自動化實施
KIF 利用 Apple 給所有控件提供的輔助屬性 accessibility attributes 來定位和獲取元素,完成界面的交互操作;結(jié)合使用 Xcode 的 XCTest 測試框架,擁有 XCTest 測試框架的特性,使得測試用例能以 command line build 工具運行并獲取測試報告。
下面介紹如何進行 KIF 自動化實施。
1. KIF 搭建
KIF 以第三方庫的形式編譯運行于工程中,搭建 KIF 之前,應(yīng)該確保工程在 Xcode 上編譯運行通過。 KIF 基于 XCTest 框架,繼承了 XCTest 的所有特性。和 XCTest 一樣,我們首先應(yīng)該在工程項目中創(chuàng)建基于 Cocoa Touch Testing Bundle 模板的 Target ,并確保創(chuàng)建的 Target 的屬性有如下設(shè)置:
- “Build Phases”:設(shè)置 Target Dependencies , UI 自動化測試固然要依賴應(yīng)用程序的 App 產(chǎn)物,所以需保證應(yīng)用程序 Target 被添加在 Test Target 的 Target Dependencies 中。
- “Build Settings”: ????設(shè)置 “Bundle loader” 為:$(BUILT_PRODUCTS_DIR)/MyApp.app/MyApp; ????設(shè)置 “Test Host” 為:$(BUILT_PRODUCTS_DIR); ????設(shè)置 “Wrapper Extensions” 為:xctest。
項目的設(shè)置準備好后,需要安裝 KIF 庫源碼到項目。即可開始 KIF 編寫用例之旅。 KIF 通過屬性值(AccessibilityLabel, AccessibilityIdentifier, AccessibilityTraits,Value…)在界面中定位元素。為了獲取到目標元素,我們必須先設(shè)置元素的 accessibility 屬性。如下,想要獲取程序中一個列表的 cell 元素,我們給列表的 cell 控件設(shè)置 accessibility 屬性(如左圖所示),設(shè)置為“Section XX Row XX”,編譯運行,即可獲得歷史列表的 cell 元素;用模擬器的 Accessibility Inspector 抓取到了這個歷史列表元素(如右圖所示):
KIF 為我們提供了對有 accessibility 屬性控件的操作接口,如下最簡單的兩個操作接口:
- 點擊一個元素:- (void)tapViewWithAccessibilityLabel:(NSString *)label;
- 等待一個元素的出現(xiàn):- (UIView *)waitForViewWithAccessibilityLabel:(NSString *)label。
在新建的 Target 同名目錄下增加一個繼承自 KIFTestCase 的類,類中編寫我們的用例,完成對界面的點擊和驗證,如下:
以上步驟都完成后, 基于KIF的簡單用例便搭建完成,點擊 Product->Test 或者快捷鍵 (?U) 即可看到我們的用例自動運行起來了。
2. 用例編寫與組織
(1)accessibility 屬性設(shè)置
accessibility 屬性是 Apple 給視覺障礙人群提供完全無障礙使用的基本屬性,該屬性表明了 UI 元素的可訪問性、是什么、做什么以及會觸發(fā)什么樣的操作。原生的 UIKit 控件默認提供了這些信息,然而,自定義的控件則需要對該屬性進行設(shè)置,設(shè)置方式可參考下面幾點:
- 設(shè)置方式:找到頁面元素所屬的代碼文件,再到代碼中找到該類的實現(xiàn),在相應(yīng)代碼處添加其屬性。
- 查看方式:設(shè)置好后,開啟模擬器的 Accessibility Inspector 功能,即可看到控件的 accessibility 屬性。
- 設(shè)置建議:設(shè)置的 AccessibilityLabel 屬性值要有實際意義(用戶可理解),因為設(shè)置這個屬性后用戶可以通過 VoiceOver 訪問;用戶不可訪問的控件,比如某些放置控件的容器等應(yīng)該設(shè)置為 AccessibilityIdentifier 。
(2)用例常用操作接口:
- UI交互操作( KIFUITestActor.h 中可查閱):
擴展:我們還可以對 KIFUITestActor 類進行擴展,利用 KIFUITestActor 中的私有函數(shù),使 AccessibilityIdentifier 代替 Label 識別元素,完成 tapThisView 、waitForView 等操作。
- 用例集操作( KIFTestCase.h 中可查閱):
- 系統(tǒng)的功能實現(xiàn)( KIFSystemTestActor.h 中可查閱):
(3)用例組織
設(shè)計實現(xiàn)單個測試用例步驟如下: * a. 設(shè)置測試所需要的環(huán)境; * b. 測試用例的測試邏輯; * c. 恢復(fù)App至此次測試前狀態(tài)。
a、c步驟可用 beforeEach、afterEach 來實現(xiàn),這樣保證了每個用例之間的獨立性和用例運行的穩(wěn)定性。 一般來說,可將用例按功能分成若干個用例集,每個用例集按校驗點或者功能點分成若干個用例,這樣方便測試用例的管理和維護。 某些含有耗費時間多、耗費資源多的公共操作的用例可以集合成一個用例集,在用例集運行前統(tǒng)一執(zhí)行。設(shè)計實現(xiàn)用例集步驟如下:
- a. 設(shè)置用例集需要的環(huán)境、公共操作;
- b. 設(shè)計各個用例;
- c. 恢復(fù) App 至用例集測試的初始狀態(tài)。
a、c步驟可用 beforeAll、afterAll 來實現(xiàn),下圖展示了一個用例集的書寫示例:
#import "TimerTests.h" #import "KIFUITestActor+AccessibilityLabelAddition.h" #import "KIFUITestActor+IdentifierAdditions.h" #import "KIFUITestActor+TimerAdditions.h" @implementation TimerTests - (void)beforeAll {[tester setDebugModel]; } - (void)afterAll {[tester resetDebugModel];[tester clearHistory]; } - (void)beforeEach {[tester setDebugModel]; } - (void)afterEach {[tester clearParams]; } - (void)testNameedTask {[tester enterText:@"myTask" intoViewWithAccessibilityLabel:@"Task Name Input"];[tester enterWorktime:10 Breaktime:4 Repetitions:5];[tester tapViewWithAccessibilityLabel:@"Start Working"];[tester waitForViewWithAccessibilityLabel:@"myTask"];[tester waitForViewWithAccessibilityLabel:@"Start Working"]; } - (void)testnoNameTask {[tester enterWorktime:10 Breaktime:4 Repetitions:5];[tester tapViewWithAccessibilityLabel:@"Start Working"];[tester waitForViewWithAccessibilityLabel:@"myTask"];[tester waitForViewWithAccessibilityLabel:@"Start Working"]; } - (void)testPresetTask {[tester tapViewWithAccessibilityLabel:@"Presets"];[tester tapRowAtIndexPath:@"Classic" inTableViewWithAccessibilityIdentifier:@"Presets List"];[tester tapViewWithAccessibilityLabel:@"Start Working"];[tester waitForViewWithAccessibilityLabel:@"myTask"];[tester waitForViewWithAccessibilityLabel:@"Start Working"]; } @end上述代碼中,我們看到許多封裝函數(shù)。為保證用例結(jié)構(gòu)清晰明朗,我們借鑒 selenium pageObject 的設(shè)計方式, 遵循如下規(guī)則:
- a. 將頁面上的對元素的發(fā)現(xiàn)、操作處理抽象為相應(yīng)的類,返回操作結(jié)果;
- b. 封裝盡可能多的工具類;
- c. 測試用例只關(guān)注用例邏輯,步驟盡量簡潔。
如下圖所示,在用例集 test suite 中,我們只保持清晰的用例邏輯;非用例邏輯的動作封裝成相應(yīng)地用例集的類 test suite additions ;因為 KIF 的開源性,我們還可以利用 KIF 的私有 API 封裝我們需要的工具 Tools 類。
(4)用例的運行獨立和 retry 機制
失敗用例是不可避免的,上述用例的組織方式,降低了用例間的依賴性,但是并不能完全消除失敗用例對后續(xù)用例執(zhí)行的影響。如果能讓每個用例獨立啟動 App 執(zhí)行 case,則能保證后執(zhí)行用例不受先執(zhí)行失敗用例的影響。如果在 case 運行失敗后,還可以進行 retry 重試,則能提高用例運行的穩(wěn)定性。xctool 工具能給我們帶來這樣的功能,我們用 xctool 命令先 build-tests 構(gòu)建 app,然后循環(huán)啟動 app 來 run-tests 用例,用例失敗后,重新執(zhí)行。下面是一個 xctool 獨立運行用例的簡單示例:
xctool build-tests -workspace myApp.xcworkspace -scheme myKIFTestScheme -sdk iphonesimulator -configuration Debug -destination platform='iOS Simulator',OS=8.3,name='iPhone 6 Plus'array=( TimerTests HistoryTests )for data in ${array[@]} doxctool -reporter pretty -reporter junit:tmp/test-report-tmp.xml -workspace myApp.xcworkspace -scheme myKIFTestScheme run-tests -only myKIFTestTarget:${data} -sdk iphonesimulator -configuration Debug -destination platform='iOS Simulator',OS=8.3,name='iPhone 6 Plus' done三、KIF 自動化的持續(xù)集成
1. 持續(xù)集成的意義與 UI 自動化測試的用例選擇
持續(xù)集成是一個自動化的周期性的集成測試過程,從檢出代碼、編譯構(gòu)建、運行測試、結(jié)果記錄、測試統(tǒng)計等都是自動完成的,無需人工干預(yù)。我們的項目都是團隊協(xié)作開發(fā),采用持續(xù)集成的優(yōu)勢顯而易見:
- 盡早盡快地發(fā)現(xiàn)集成錯誤,保證團隊開發(fā)人員提交代碼的質(zhì)量,減輕軟件發(fā)布時的壓力;
- 自動完成集成中的環(huán)節(jié),有利于減少集成過程的重復(fù)工作以節(jié)省時間、費用和工作量;
持續(xù)集成最大的好處在于能夠盡早高效發(fā)現(xiàn)問題,降低解決問題的成本。而發(fā)現(xiàn)問題的手段主要就是測試。 根據(jù) Martin Fowler 的測試理論,測試應(yīng)該遵循如下測試金字塔組合,測試金字塔最底層是單元測試,然后是集成測試,繼而是面向應(yīng)用程序服務(wù)層的中間層測試,最高層是面向用戶的業(yè)務(wù)邏輯測試:
測試自動化的測試層級越多,持續(xù)集成平臺就能產(chǎn)生越大的價值。 UI 測試目標是覆蓋最核心的代碼,盡可能去掉依賴,讓不穩(wěn)定因子降到最低,這樣既保證自動化測試層級的全面性,又保證持續(xù)集成的穩(wěn)定構(gòu)建,降低測試的投入產(chǎn)出比。因此,在我們的 UI 自動化測試中,我們選擇核心功能的冒煙用例來完成持續(xù)集成中的測試金字塔。
2. Jenkins 上完成基于 KIF 的 UI 自動化持續(xù)集成搭建
Jenkins 是一個開源的持續(xù)集成工具,提供了一種易于使用的持續(xù)集成系統(tǒng),使開發(fā)者從繁雜的集成中解脫出來,專注于更為重要的業(yè)務(wù)邏輯實現(xiàn)上。 Jenkins 以 Job 為單位運行項目,一個 Job 的工作流程為:在指定的時機,選擇合適的 salve 節(jié)點,從版本管理系統(tǒng)上獲取對應(yīng)的源碼,使用命令行腳本或者 maven 或者 ant 進行構(gòu)建,構(gòu)建后歸檔文件,處理報告,如果構(gòu)建失敗那么就通過郵件進行反饋等。 Job 的觸發(fā)時機主要有3種選擇:
- “Build after other project are build”:表示在其他某個項目build后觸發(fā),比如我們可以在某個提測Job構(gòu)建之后,立即構(gòu)建我們的 UI 自動化來驗證這個提測的可行性;
- “Build periodically”:表示按時間觸發(fā),我們可以選擇這個讓 Job 做 Daily Build 來進行持續(xù)構(gòu)建觀察;
- “Poll SCM”:表示允許用戶讓 Jenkins 定期查詢某一個項目的代碼庫,如果有代碼變動則觸發(fā)執(zhí)行任務(wù),這種觸發(fā)非常適合集成測試項目,以此驗證代碼庫變動是否能測試通過。
我們希望在代碼改動發(fā)生的時候就做到盡早發(fā)現(xiàn)代碼改動帶來的問題,所以使用 “Poll SCM” 在當(dāng)代碼倉庫有新的 pull request 的時候觸發(fā)相應(yīng) Job 完成構(gòu)建,Job 的執(zhí)行結(jié)果作為這個 pull request 能否合入的衡量指標之一;同時為支持客戶端支持 daily build ,Job 使用 “Build periodically” 在每天 daily build 打包前完成一次自動構(gòu)建。 Job 需要支持命令行構(gòu)建才能實現(xiàn)持續(xù)集成,如上一部分提到,我們可以借助 xcodebuild/xctool 實現(xiàn)單命令行構(gòu)建。同時為了衡量 Job 的執(zhí)行結(jié)果,我們需要在 Job 執(zhí)行完成后生成相應(yīng)的測試報告和代碼覆蓋率報告,使用 xcodebuild/xctool 這樣的命令行工具,只需要配置相關(guān)的參數(shù)即可獲取相應(yīng)的 XML 測試報告文件。 Jenkins 中 JUnit Plugin 插件可以將 XML 形式的測試報告轉(zhuǎn)化成一種隨時間推移的測試結(jié)果圖表,向我們展示測試的結(jié)果和測試的穩(wěn)定性; Cobertura plugin 插件可以將 XML 形式的覆蓋率文件轉(zhuǎn)化成一種隨時間推移的代碼覆蓋率圖表。如下圖是 Job 中測試報告的代碼覆蓋率和測試結(jié)果的示例,通過下面的圖表,我們可以清晰地看到測試是否通過,檢查代碼的測試覆蓋范圍,并對比歷史的測試結(jié)果和代碼覆蓋率來推斷和定位問題。
3. KIF 自動化測試在 Jenkins 持續(xù)集成過程中遇到的問題
(1)設(shè)備重置
我們的測試用例覆蓋了第一次安裝啟動的操作。在初期,這個用例經(jīng)常失敗。經(jīng)過排查發(fā)現(xiàn),持續(xù)集成系統(tǒng)中的模擬器設(shè)備重置操作并沒有覆蓋所有的設(shè)備,UI 測試 Job 運行時,Job 選擇的模擬器設(shè)備上可能遺留了其他 Job 構(gòu)建的相同的 app 產(chǎn)物,導(dǎo)致我們的 Job 構(gòu)建產(chǎn)物并不是第一次安裝啟動。所以在腳本中我們遍歷所有模擬器設(shè)備,將其進行重置。
(2)鍵盤敲擊延遲
我們的測試用例在輸入框輸入文字時,經(jīng)常出現(xiàn)輸入不全而導(dǎo)致失敗的問題。比如在輸入框中輸入 ‘beijing’ ,失敗后提示:Failed to get text in field; instead, it was ‘beiji’ 。經(jīng)過排查,發(fā)現(xiàn)持續(xù)集成系統(tǒng)中的機器性能有高有低,在低性能機器中更容易發(fā)生此問題,再研究 KIF 框架源碼發(fā)現(xiàn),KIF 默認設(shè)置的鍵盤敲擊時延為一個常數(shù),對于低性能機器來說這個敲擊時延較短,容易漏掉輸入,所以我們在 KIFTypist.m 源碼文件中適當(dāng)增加 (NSTimeInterval) keystrokeDelay 的時長來避免輸入不全的問題。
(3)多個系統(tǒng)彈窗確認
前面我們提到過,KIF 支持對系統(tǒng)彈窗的處理,即接口 acknowledgeSystemAlert ,它能幫我們確認一個系統(tǒng)彈窗。但是我們的應(yīng)用程序在啟動時系統(tǒng)彈窗并不止一個,并且在不同設(shè)備上,因系統(tǒng)設(shè)置不同,系統(tǒng)彈窗的個數(shù)是不確定的。所以,直接使用 acknowledgeSystemAlert 并不能幫我們解決問題。因為 KIF 的開源性,我們在 KIF 框架源碼 acknowledgeSystemAlert 函數(shù)中做了一次 while 循環(huán)處理,處理了出現(xiàn)的任意多個系統(tǒng)彈窗的情況,從而解決了問題。
參考文獻
總結(jié)
以上是生活随笔為你收集整理的基于 KIF 的 iOS UI 自动化测试和持续集成的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: @FeignClient中的@Reque
- 下一篇: 震惊!丧心病狂的夕小瑶推出新一轮写作计划