KIF-- iOS UI 自動化測試探索

KIF-- iOS UI 自動化測試探索

在我們探索自動化測試之前,我們先了解一下自動化測試的優缺點和還有,什么樣的業務適合自動化測試。

自動化測試

自動化測試就是寫一些測試代碼,用代碼代替人工去完成模塊和業務的測試。

其實不管是開發還是測試,如果你在不斷的做重復性工作的時候,就應該問自己一個問題:是不是有更高效的辦法?

  • 自動化測試的優點:

    • 測試速度快,避免重復性工作
    • 避免regression(回退),讓開發更有信心去修改,優化甚至重構代碼。(有測試代碼做依托,不怕業務邏輯丟失,混亂)
    • 測試結果一致性
    • 自動化測試的實現,有助于持續集成的可行性和可靠性提升
    • 強化開發人員編寫高質量代碼(自動化測試,不通過,不能提交合并)
  • 自動化測試的一些缺點:

    • 開發和維護成本高,需要專業的測試人員
    • 不能完全替代人工測試
    • 本身的測試代碼的準確性,還有無法保證測試的準確性-讓代碼去判斷一段邏輯是否正確還是可行的,但是要判斷一個控件是否顯示正確,代碼很難實現
    • 團隊的建設和方案的選取等一系列的問題

所以,在做自動化測試之前,我們首先要針對項目提出幾個問題:

  1. 這個測試業務的變動是否頻繁
  2. 這個測試業務是否屬于核心功能
  3. 編寫測試代碼的成本是多少,是否劃算
  4. 自動化測試能保證測試結果的準確么

通常我們只會選擇那些業務穩定,需要頻繁測試的部分來編寫自動化腳本,其余的依然得人工測試,人工測試是 iOS App 開發中不可缺少的一部分。

測試種類

從是否接觸源代碼的角度來分類: 測試分為黑盒和白盒。

白盒測試的時候,測試人員是可以直接接觸待測試App的源代碼的。白盒測試更多的是單元測試,測試人員針對各個單元進行各種可能的輸入分析,然后測試其輸出。白盒測試的測試代碼通常由iOS開發編寫。

黑盒測試。黑盒測試的時候,測試人員不需要接觸源代碼。是從App層面對其行為以及UI的正確性進行驗證,黑盒測試由iOS測試完成。

從業務層次來說 iOS 測試通常只有以下兩個層次:
Unit,單元測試,保證每一個類都能夠正常工作

UI,UI 測試,也叫做集成測試,從業務層的角度保證各個業務可以正常工作。

框架選擇

測試框架五花八門,一定要選擇適合自己團隊的,測試效率,集成難易度,維護難易度等等,選擇框架的時候我們要考慮一下幾個方面:

  1. 測試代碼編寫的成本
  2. 是否可調式,調試是否便利
  3. 測試框架本身的穩定性
  4. 測試報告是否詳細(截圖,代碼覆蓋率...)
  5. WebView 的支持(H5混合的 App)
  6. 自定義控件的測試支持
  7. 是否需要源代碼
  8. 是否需要連著電腦和設備
  9. 是否支持持續集成

其中單元測試,我們上一篇著重介紹了 BDD 的老牌測試框架 Kiwi ,就不多說了。

UI測試,UI 測試的框架有很多,有的是以 UI Automation 為基礎,對其進行補充和優化,包括擴展型 UI Automation 和驅動型 UI Automation。
還有一些框架類型是私有 API 和注入編譯型等。
在以上分類中挑選具有代表性的自動化框架:UI Automation、Appium、KIF、Frank、UI Testing 進行對比,下表是這幾種測試框架的特點對比:


圖片來源網絡

結合上面我們選擇框架要考慮的幾個方面,KIF框架已經展現了它的優勢,并且KIF使用XCTest框架,使得其測試流程iOS程序的單測無異,可完全復用單測的持續集成流程,維護持續集成的成本相對降低;另外,KIF是一個活躍的開源測試框架,可擴展性好,升級更新快,有活躍社區來探討和解決使用過程中遇到的問題。就是今天我們要介紹的重點,KIF

KIF

KIF的全稱是Keep it functional。利用 Apple 給所有控件提供的輔助屬性 accessibility attributes來定位和獲取元素,完成界面的交互操作;結合使用 Xcode 的 XCTest 測試框架,擁有 XCTest 測試框架的特性,使得測試用例能以 command line build 工具運行并獲取測試報告。

KIF 搭建

我們首先應該在工程項目中創建基于 Cocoa Touch Testing Bundle 模板的 Target ,并確保創建的 Target 的屬性有如下設置:

“Build Phases”:設置Target Dependencies,UI自動化測試固然要依賴應用程序的App產物,所以需保證應用程序 Target 被添加在 Test Target 的 Target Dependencies 中。

“Build Settings”:設置 “Bundle loader”為:$(BUILT_PRODUCTS_DIR)/MyApp.app/MyApp;MyApp使你自己項目的路徑

設置 “Test Host” 為:$(BUILT_PRODUCTS_DIR);

設置 “Wrapper Extensions” 為:xctest。

  • cocoaPods導入:
target 'Your Apps' do
  ...
end

target 'Acceptance Tests' do
  pod 'KIF', :configurations => ['Debug']
end
  • 手動導入,最新的framework 方式導入,非常頭疼
    • 下載KIF源碼,選擇 KIFFramework這個 scheme編譯,products 里面生成KIF.framework,show in finder 把它拷貝到我們需要測試的項目里面去
    • 打開 Xcode file new Add target 選擇 iOS Unit Testing Bundle 或者 iOS UI Testing bundle 設置一個自己喜歡的target 名稱
    • 選擇我們新建的 target 點擊Build Phases下的Link Binary With Libaries,添加我們剛拷貝過來的KIF.framework,系統依賴庫QuartzCore.frameworkCoreGraphics.framework
    • 然后選擇這個 target 的Build Settings,在Other Linker Flags里面添加 -framework IOKit-ObjC這兩選項
    • 接著設置User Header Search PathsFramework Search Paths的路徑為我們新建的 target
    • 最后,設置Bundle Loader為"$(BUILT_PRODUCTS_DIR)/MyApplication.app/MyApplication" 里面的 MyApplication是自己自己項目的名字
    • 最后一步,最重要的一部,先把項目跑一遍,生成MyApplication.app之后再執行command + U開始 testing
  • 現在可以開始寫我們的測試用例了
    開始之前,我想來張圖,KIF 基于蘋果給所有控件添加的一個accessibility 屬性來實現的,所以在 Storyboard 上我們有兩種方式設置

還可以通過代碼設置:

[alert setAccessibilityLabel:@"Label"];
[alert setAccessibilityValue:@"Value"];
[alert setAccessibilityTraits:UIAccessibilityTraitButton];

為了跟原業務代碼隔離,我們在業務代碼中應該建立宏來隔離我們設置 accessibility 屬性的代碼,如下面的例子:

#ifdef DEBUG
[tableView setAccessibilityValue:@"Main List Table"];
#endif

#ifdef KIF_TARGET (這個值需要在build settings里設置)
[tableView setAccessibilityValue:@"Main List Table"];
#endif

測試用例的編寫和組織

  • accessibility屬性設置

accessibility 屬性是Apple給視覺障礙人群提供完全無障礙使用的基本屬性,該屬性表明了UI元素的可訪問性、是什么、做什么以及會觸發什么樣的操作。原生的UIKit控件默認提供了這些信息,然而,自定義的控件則需要對該屬性進行設置,設置方式可參考下面幾點:

  1. 設置方式:storyboard 設置,代碼設置
  2. 查看方式:Xcode打開Open Developer Tool開啟模擬器的 Accessibility Inspector功能,即可看到控件的 accessibility 屬性。
  3. 設置建議:設置的 AccessibilityLabel 屬性值要有實際意義(用戶可理解)因為設置這個屬性后用戶可以通過 VoiceOver訪問;用戶不可訪問的控件,比如某些放置控件的容器等應該設置為 AccessibilityIdentifier 。
  • KIF 常用操作接口(KIFUITestActor.h里可查閱)
 tapThisView:- (void)tapViewWithAccessibilityLabel:(NSString *)label;
 
 waitForView:- (UIView *)waitForViewWithAccessibilityLabel:(NSString *)label;
//注意:函數返回了對應View的指針,可以對返回值取數據,從而進行一些判斷

 enterTextIntoView: - (void)enterText:(NSString *)text intoViewWithAccessibilityLabel:(NSString *)label;
 
 tapRowOnTableView:- (void)tapRowAtIndexPath:(NSIndexPath *)indexPath inTableViewWithAccessibilityIdentifier:(NSString *)identifier NS_AVAILABLE_IOS(5_0); 
 
 dismisses a system alert: - (void)acknowledgeSystemAlert;

擴展:我們還可以對 KIFUITestActor 類進行擴展,利用 KIFUITestActor 中的私有函數,使 AccessibilityIdentifier 代替 Label 識別元素,完成 tapThisView 、waitForView 等操作。

  • KIF測試用例集操作(KIFTestCase.h 中可查閱)
/*!
 * @abstract This method runs once before executing the first test in the class.
 * @discussion This should be used for navigating to the starting point in the app where all tests will start from.  Because this method is not guaranteed to run in the same instance as tests, it should not be used for setting up instance variables but can be used for setting up static variables.
 */
 /*
 在本類中第一個 test case 執行前執行一次,用來執行本類中各個測試函數的公共操作
 //注意:因為不能保證這個方法與 test case 是同一個類實例,所以不能用來設置實例變量的值,但是可以設置靜態變量
 */
- (void)beforeAll;

/*!
 * @abstract This method runs before each test.
 * @discussion This should be used for any common tasks required before each test.  Because this method is guaranteed to run in the same instance as tests, it can be used for setting up instance variables.
 */
 //在每個具體 test case 執行前執行一次,用來執行各個函數需要的測試環境
 //注意因為確保這個方法與 test case 是同一個類實例,可以用來設置實例變量

- (void)beforeEach;

/*!
 * @abstract This method runs after each test.
 * @discussion This should be used for restoring the app to the state it was in before the test.  This could include conditional logic to recover from failed tests.
 */
 //在每個具體 test case 執行完之后執行一次,用來清除狀態,恢復至 test 之前的狀態,可以包含一些條件判斷邏輯,從失敗的 test case 中恢復,以確保不影響之后的測試
 
- (void)afterEach;

/*!
 * @abstract This method runs once after executing the last test in the class.
 * @discussion This should be used for navigating back to the initial state of the app, where it was before @c beforeAll.  This should also be used for tearing down any static methods created by @c beforeAll.
 
 */
 //執行完本類的最后一個 test case 之后執行一次,用于將 App 恢復至測試的初始狀
- (void)afterAll;

/*!
 * @discussion When @c YES, rather than failing the test and advancing on the first failure, KIF will stop executing tests and begin spinning the run loop.  This provides an opportunity for inspecting the state of the app when the failure occurred.
 */
  • 系統的功能實現(KIFSystemTestActor.h中可查閱)
模擬用戶旋轉設備:- (void)simulateDeviceRotationToOrientation:(UIDeviceOrientation)orientation;
對當前屏幕截圖并存儲到硬盤中:- (void)captureScreenshotWithDescription:(NSString *)description;

用例組織

  • 設計單個測試用例
    • a.設置測試所需要的環境
    • b.測試用例的具體測試邏輯
    • c.恢復 App 至此次測試前的狀態

a,c可用beforeEachalterEach來實現,這樣保證了每個用例之間的獨立性和穩定性

一般來說,可將用例按功能分成若干個用例集,每個用例集按校驗點或者功能點分成若干個用例,這樣方便測試用例的管理和維護。某些含有耗費時間多,耗費資源多的公共操作的用例可以集合成一個用例集,在用例集運行前統一執行。

  • 設計用例集
    • 1.設置用例集需要的環境,公共操作
    • 2.設計各個用例
    • 3.恢復 App 至用例集測試的初始狀態
      1和3 步驟可以用beforeAllafterAll來實現。下面簡單展示一個用例集的書寫:
#import "CrazyTests.h"

#import <KIF/KIFUITestActor-IdentifierTests.h>
#import <KIF/KIFUITestActor-ConditionalTests.h>

@implementation CrazyTests
- (void)beforeAll
{
    [self setTestModel];
}

- (void)afterAll
{
    [self resetTestModel];
    [self cleanHistory];
}

- (void)beforeEach
{
    [self setTestModel];
}

- (void)afterEach
{
    [self cleanParams];
}

- (void)testNameTask
{
    [tester enterText:_pp.nickName intoViewWithAccessibilityLabel:@"name"];
    [tester enterText:_pp.realName intoViewWithAccessibilityLabel:@"password"];
    [tester tapViewWithAccessibilityLabel:@"login"];
}

#pragma mark-- setting
- (void)setTestModel
{
    _pp = [Person new];
    _pp.age = 20;
    _pp.nickName = @"crazy";
    _pp.realName = @"hey";
    _pp.cardId = @"123456789";
    
}
- (void)resetTestModel
{
    _pp = nil;
}
- (void)cleanHistory
{
    _pp = nil;
}
- (void)cleanParams
{
    _pp = nil;
}

上述例子,只是簡單說明。我們書寫用例集應該遵循如下規則:

  1. 將頁面上對元素的發現,操作處理抽象為相應的類,返回操作結果
  2. 封裝盡可能多的工具類
  3. 測試用例只關注用例邏輯,步驟盡量簡潔

我們可以利用 KIF 的私有 api 封裝我們的工具類。

用例的獨立運行和 retry 機制

失敗用例是不可避免的,上述用例的組織方式,降低了用例間的依賴性,但是并不能完全消除失敗用例對后續用例執行的影響。如果能讓每個用例獨立啟動 App 執行 case,則能保證后面執行用例不受執行失敗用例的影響。如果在 case 運行失敗后,還可以進行 retry 重試,能提高用例運行的穩定性。xctool這個工具能給我們帶來這樣的功能,我們用 xctool 命令先 build-tests 構建 App,然后循環啟動 App 來 run-tests用例,用例失敗后,重新執行。下面是是一個 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[@]}
do 
        xctool  -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

一般我們測試,團隊大了或者分工特別細的話,就需要接入自動化持續集成。后續大家可以自行了解,主要框架有Jenkins Fastlane等。

優化測試用例

當測試用例寫多了,我們也會重構我們的測試用例代碼。通常,我們應該從幾個角度去考慮:

  • 不要測試私有方法(封裝是 OOP 的核心思想之一,不要為了測試破壞封裝)
  • 對測試用例分組(功能,業務相似)
  • 對單個用例保證測試獨立(不受之前測試的影響,不影響之后的測試),這是測試是否準確的核心
  • 提取公共的代碼和操作,減少 copy/paste這樣的工作,測試用例是上層調用,只關心業務邏輯,不關心內部代碼實現

總結

KIF 因為是建立在 XCTest 框架之上的,所以非常適合我們開發者上手,而且利用私有 API,我們可以很方便的測試 UI 層面和單元測試等

參考:

iOS自動化測試的那些干貨

基于 KIF 的 iOS UI 自動化測試和持續集成

解放你的雙手—iOS自動測試基礎

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容

  • 前言 如果有測試大佬發現內容不對,歡迎指正,我會及時修改。 大多數的iOS App(沒有持續集成)迭代流程是這樣的...
    默默_David閱讀 1,682評論 0 4
  • 大多數的iOS App (沒有持續集成)迭代流程是這樣的: 也就是說,測試是發布之前的最后一道關卡。如果bug不能...
    伯牙呀閱讀 4,902評論 1 22
  • 前文:根據Martin Fowler 的測試理論,測試應該遵循如下測試金字塔組合,測試金字塔最底層是單元測試,然后...
    小小小蚍蜉閱讀 1,398評論 0 2
  • 0.小目標 關于UI自動化的定義,我想要的是自動地按照流程去點擊頁面、輸入數據,不需要人去參與,節省人工時間。比如...
    孢子菌閱讀 15,608評論 10 47
  • 從兒童進學校的第一天起,就要善于看到并不斷鞏固和發展他們身上所有好的東西。 今天峻峻、恒恒、延延也有自己穿襪子呢,...
    紅黃藍塔塔班2閱讀 204評論 0 0