iOS UITests(UI自動(dòng)化測試 實(shí)現(xiàn))
本文Demo地址
[toc]
簡介
1.UI Tests是一個(gè)自動(dòng)測試UI與交互的Testing組件,它可以通過編寫代碼、或者是記錄開發(fā)者的操作過程并代碼化,來實(shí)現(xiàn)自動(dòng)點(diǎn)擊某個(gè)按鈕、視圖,或者自動(dòng)輸入文字等功能。
2.在實(shí)際的開發(fā)過程中,隨著項(xiàng)目越做越大,功能越來越多,僅僅靠人工操作的方式來覆蓋所有測試用例是非常困難的,尤其是加入新功能以后,舊的功能也要重新測試一遍,這導(dǎo)致了測試需要花非常多的時(shí)間來進(jìn)行回歸測試,這里產(chǎn)生了大量重復(fù)的工作,而這些重復(fù)的工作有些是可以自動(dòng)完成的,這時(shí)候UI Tests就可以幫助解決這個(gè)問題了.

最低要求
Xcode7
iOS 9
使用
1.添加UI Teste Targets
新工程 創(chuàng)建工程的時(shí)候勾選 Inclide UI Tests

現(xiàn)有的工程 新建一個(gè) Targets 選擇 iOS UI Testing Bundle


添加完成 添加完成可以看到UITests如圖:

自動(dòng)化測試代碼的編寫
方法1.自己看著界面編寫
參考文章下面的代碼演示
iOS UITests(自動(dòng)化測試)常用類參考文檔
參考Demo
方法2.錄制后修改
1.點(diǎn)擊UITests類 下面的 紅色的錄制按鈕

然后APP會(huì)啟動(dòng),你只需要對APP進(jìn)行界面操作,系統(tǒng)就會(huì)在你光標(biāo)最后停留的位置開始生成與你操作對應(yīng)的代碼,然后你稍作修改就可以用了。
看大大大圖

自動(dòng)生成的代碼

稍作修改,把里面的Unicode編碼 轉(zhuǎn)成中文,如下

這樣就可以直接運(yùn)行了
運(yùn)行自動(dòng)化測試
1.command + U 運(yùn)行UITests類中 每一個(gè)以test開頭的方法 如:'- (void)allTestExample' '- (void)testHome' 個(gè)人感覺是沒有順序的

2.點(diǎn)擊每一個(gè)以
test開頭的方法前面的 播放 按鈕(運(yùn)行單個(gè)方法)
涉及到的新類
iOS UITests(自動(dòng)化測試)常用類參考文檔
XCUIApplication - 啟動(dòng)程序
1.簡介
這個(gè)類掌管應(yīng)用程序的生命周期,代表被測應(yīng)用繼承
XCUIElement - 元素
1.簡介
繼承NSObject,實(shí)現(xiàn)協(xié)議XCUIElementAttributes, XCUIElementTypeQueryProvider
可以表示系統(tǒng)的各種UI元素
XCUIElementQuery - 查找元素
用于定位用戶界面元素的查詢
iOS UITests(自動(dòng)化測試)常用類參考文檔
代碼演示
https://github.com/ywdonga/iOSUITestsDemo
為了更好的演示 我用storyboard搭建了一個(gè)可以簡單跳轉(zhuǎn)的界面

1.我們從首頁的測試開始(未登錄的首頁)
測試首頁我們首先得判斷是否已經(jīng)登錄,因?yàn)橐训卿浐臀吹卿浭菬o法進(jìn)入 我的、資訊 這兩個(gè)界面的。在選擇這兩個(gè)頁面的時(shí)候會(huì)直接彈出登錄界面,測試流程就會(huì)有差異。所以就得先判斷當(dāng)前狀態(tài)是否是已登錄狀態(tài)。
1.1 判斷登錄狀態(tài)
因?yàn)槲覀儾荒茉L問原有APP的任何變量,所以只能通過某個(gè)界面特有的元素Element 來進(jìn)行判斷。在這里我們看到 如果未登錄的話 點(diǎn)擊‘我的’ 一定會(huì)跳轉(zhuǎn)到‘登錄’界面,而在登錄界面有一個(gè)‘登錄’Label. 在這里 我就通過 登錄界面中的一個(gè) 文字為‘登錄’的Label元素 作為判斷對象(當(dāng)然你可以根據(jù)自己的想法去定,但是一定要確保元素的唯一性)。
流程如下
Paste_Image.png
/** 判斷是否在登陸界面 */
- (BOOL)isLogin{
BOOL isLogin;
//進(jìn)入我的界面
XCUIElementQuery *tabBarsQuery = self.app.tabBars;
XCUIElement *tableBarButton = tabBarsQuery.buttons[@"我的"];
!tableBarButton.exists?:[tableBarButton tap];
//獲取標(biāo)志性Element loginLabel
XCUIElement *loginLabel = self.app.staticTexts[@"loginLabel"];
//判斷是否登陸
if(loginLabel.exists){//進(jìn)入登陸界面(當(dāng)前狀態(tài)未登陸)
XCUIElement *cancerButton = self.app.buttons[@"取消"];
!cancerButton.exists?:[cancerButton tap];
isLogin = NO;
}else{//進(jìn)入我的頁面(當(dāng)前狀態(tài)已經(jīng)登陸)
tableBarButton = tabBarsQuery.buttons[@"首頁"];
!tableBarButton.exists?:[tableBarButton tap];
isLogin = YES;
}
return isLogin;
}
上面代碼中為何定義了一個(gè)變量(BOOL isLogin)去接收登錄狀態(tài),為何不直接用 ‘登錄’元素是否存在(loginLabel.exists)作為結(jié)果直接返回呢?
原因在于,在判斷登錄時(shí),我們進(jìn)入到其他界面了,為了不影響后面的測試流程,我們在判斷完后最好要回到'首頁'。然而在我們回到首頁時(shí)候,登錄元素在首頁是找到不到的,'loginLabel.exists'直接為NO了。
1.2 退出登錄
判斷完登錄狀態(tài)后,就會(huì)出現(xiàn)兩種情況了,登錄/未登錄,由于我們現(xiàn)在的目的是測試未登錄的首頁,那么如果是已經(jīng)登錄了就先退出登陸
logOut.gif
流程就是
判斷是否登陸 ->已經(jīng)登陸 去‘我的’界面找到‘退出登陸’按鈕,點(diǎn)擊退出登陸
- (void)testLogOut{
//判斷是否登陸
if([self isLogin]){
NSLog(@"--> 開始退出登陸");
//進(jìn)入我的頁面
XCUIElement *tableBarButton = self.app.tabBars.buttons[@"我的"];
!tableBarButton.exists?:[tableBarButton tap];
//向上滑一點(diǎn) 避免小屏幕看不到按鈕
XCUIElement *tableView = [self.app.tables elementAtIndex:0];
[tableView swipeUp];
XCUIElementQuery *tablesQuery = self.app.tables;
[tablesQuery.buttons[@"退出登陸"] tap];
//彈窗點(diǎn)擊
[self alertViewTap];
//判斷是否登陸成功
XCTAssert(![self isLogin], @"退出登陸失敗,快查查原因");
NSLog(@"---> 成功退出登陸");
}else{
NSLog(@"---> 本來就沒有登陸");
}
}
1.3 測試未登陸首頁
未登錄首頁測試,目前我寫的Demo只是對首頁的每個(gè)元素進(jìn)行查找和簡單的操作
如圖
home.gif
- (void)testHome{
//判斷是否登陸
if([self isLogin]){
[self testLogOut];//退出登陸
}
//UISegmentedControl (可見文字)
XCUIElement *segmentedButton = self.app.segmentedControls.buttons[@"Second"];
!segmentedButton.exists?:[segmentedButton tap];
segmentedButton = self.app.segmentedControls.buttons[@"First"];
!segmentedButton.exists?:[segmentedButton tap];
//UITabBarController
XCUIElementQuery *tabBarsQuery = self.app.tabBars;
XCUIElement *tableBarButton = tabBarsQuery.buttons[@"我的"];
!tableBarButton.exists?:[tableBarButton tap];
tableBarButton = self.app.buttons[@"取消"];
!tableBarButton.exists?:[tableBarButton tap];
//UISlider (通過標(biāo)簽)
XCUIElement *slider1 = self.app.sliders[@"slider1"];
!slider1.exists?:[slider1 adjustToNormalizedSliderPosition:0];
!slider1.exists?:[slider1 adjustToNormalizedSliderPosition:1];
//UISwitch (通過標(biāo)簽)
XCUIElement *switch1 = self.app.switches[@"switch1"];
!switch1.exists?:[switch1 tap];
!switch1.exists?:[switch1 tap];
//UIStepper (通過順序)
XCUIElement *stepper = [self.app.steppers elementAtIndex:0];
XCUIElement *incrementButton = stepper.buttons[@"Increment"];
[incrementButton tap];
[incrementButton tap];
[incrementButton tap];
XCUIElement *decrementButton = stepper.buttons[@"Decrement"];
[decrementButton tap];
[decrementButton tap];
[decrementButton tap];
//tableview (通過順序)
XCUIElement *tableview = [self.app.tables elementAtIndex:0];
[tableview swipeDown];
[tableview swipeUp];
[tableview swipeDown];
//cells
XCUIElementQuery *cellQuery = tableview.cells;
for (int i=0; i<5; i++) {
//cell (通過順序)
[self cellTapAtIndex:i withCells:cellQuery];
//UIAlertView
[self alertViewTap];
}
}
1.4 登錄測試(多賬號)
這里我們準(zhǔn)備了多個(gè)賬號,只有最后一個(gè)是對的,然后依次登錄,如果全部登錄失敗就會(huì)進(jìn)入斷言
XCTAssert((loginIndex<self.accountArr.count), @"所有賬號都無法登陸");
準(zhǔn)備一個(gè)賬號數(shù)組 填入所有賬號
- (NSArray *)accountArr{
if(!_accountArr.count){
_accountArr = @[
@{@"name":@"zhangsan", @"pwd":@"123456"},//賬號1
@{@"name":@"lisis", @"pwd":@"123456"},//賬號2
@{@"name":@"wangmazi", @"pwd":@"123456"},//賬號3
@{@"name":@"iss", @"pwd":@"123456"},//賬號4
];
}
return _accountArr;
}
效果圖

代碼
- (void)testLogin{
if([self isLogin]){
NSLog(@"--> 測試登陸 已經(jīng)是登陸狀態(tài)");
return;
}
NSLog(@"---> 開始測試登陸");
//UITabBarController
XCUIElementQuery *tabBarsQuery = self.app.tabBars;
XCUIElement *tableBarButton = tabBarsQuery.buttons[@"我的"];
!tableBarButton.exists?:[tableBarButton tap];
XCTAssert((loginIndex<self.accountArr.count), @"所有賬號都無法登陸");
//獲取用戶名和密碼
NSDictionary *accountDict = self.accountArr[loginIndex];
NSString *userName = accountDict[@"name"];
NSString *pwd = accountDict[@"pwd"];
//UITextField
XCUIElement *nameTextField = self.app.textFields[@"用戶名"];
!nameTextField.exists?:[nameTextField tap];
//輸入用戶名之前先清除之前的文本
XCUIElement *clearButton = self.app.buttons[@"Clear text"];
!clearButton.exists?:[clearButton tap];
//輸入用戶名
[nameTextField typeText:userName];
//隱藏鍵盤
[self hindKeyBoard];
XCUIElement *pwdTextField = self.app.secureTextFields[@"密碼"];
!pwdTextField.exists?:[pwdTextField tap];
//輸入密碼
!pwdTextField.exists?:[pwdTextField typeText:pwd];
//隱藏鍵盤
[self hindKeyBoard];
//開始登陸
XCUIElement *loginButton = self.app.buttons[@"登錄"];
!loginButton.exists?:[loginButton tap];
sleep(1);//等待登陸
//根據(jù)標(biāo)識查看是否登陸成功
XCUIElement *loginLabel = self.app.staticTexts[@"loginLabel"];
if(loginLabel.exists){//登陸失敗
//清除提示框
[self alertViewTap];
NSLog(@"---> 賬號%@ 密碼%@ 登陸失敗", userName, pwd);
//換個(gè)賬號 重新登陸
loginIndex ++;
[self testLogin];
}else{//登陸成功
NSLog(@"---> 賬號%@ 密碼%@ 登陸成功", userName, pwd);
XCUIElement *tableBarButton = tabBarsQuery.buttons[@"我的"];
!tableBarButton.exists?:[tableBarButton tap];
tableBarButton = tabBarsQuery.buttons[@"首頁"];
!tableBarButton.exists?:[tableBarButton tap];
NSLog(@"---> 登陸測試完成");
}
}
1.5 資訊頁面測試
測試之前必須是已登錄狀態(tài),才可以順利進(jìn)入'資訊'頁面,所以得先判斷是否登錄,如果沒有登錄的話 必須先登錄

- (void)testInformation{
NSInteger loginCount = 0;
if([self isLogin]){//已經(jīng)登陸
NSLog(@"---> 開始測試資訊頁面");
XCUIElement *information = self.app.tabBars.buttons[@"資訊"];
!information.exists?:[information tap];
//點(diǎn)擊
XCUIElement *button = self.app.buttons[@"標(biāo)簽2"];
[button tap];
button = self.app.buttons[@"標(biāo)簽3"];
[button tap];
button = self.app.buttons[@"標(biāo)簽4"];
[button tap];
button = self.app.buttons[@"標(biāo)簽5"];
[button tap];
button = self.app.buttons[@"標(biāo)簽6"];
[button tap];
button = self.app.buttons[@"標(biāo)簽1"];
[button tap];
//滑動(dòng)
XCUIElement *swipeView = [self.app.scrollViews elementAtIndex:0];
[swipeView swipeRight];
[swipeView swipeLeft];
[swipeView swipeLeft];
[swipeView swipeLeft];
[swipeView swipeLeft];
[swipeView swipeLeft];
[swipeView swipeLeft];
NSLog(@"---> 資訊頁面測試完成");
}else{//未登陸
XCTAssert((loginCount<MaxLoginNumber), @"");
NSLog(@"---> 在資訊頁面發(fā)現(xiàn)未登錄 開始第 %@ 次登陸", @(loginCount));
loginCount ++;
//發(fā)起登陸
[self testLogin];
//繼續(xù)測試資訊
[self testInformation];
}
}


