iOS 單元測(cè)試及自動(dòng)化測(cè)試(只看這篇就夠了)

目錄

  • 一李请、怎么添加測(cè)試類
  • 二、怎么運(yùn)行測(cè)試類
  • 三厉熟、怎么查看覆蓋率
  • 四导盅、測(cè)試類怎么編寫(xiě)(一、Test)
  • 五揍瑟、測(cè)試類怎么編寫(xiě)(二白翻、UITest)
  • 六、UITest例子
  • 七月培、定位元素
    1嘁字、UITest類名介紹
    2、元素獲取方法
    3杉畜、定位元素
  • 八纪蜒、元素操作
  • 九、WebDriverAgent的使用
  • 十此叠、使用Appium進(jìn)行自動(dòng)化測(cè)試
    1纯续、安裝Appium-Desktop
    2、安裝appium-doctor
    3灭袁、更新Appium中的WebDriverAgent

前言

單元測(cè)試及自動(dòng)化測(cè)試(小白和大神都一定要了解的知識(shí))

一猬错、怎么添加測(cè)試類

二、怎么運(yùn)行測(cè)試類

有三種運(yùn)行這個(gè)測(cè)試類的方法:
1茸歧、Product\Test 或者 Command-U倦炒。這實(shí)際上會(huì)運(yùn)行所有測(cè)試類。

2软瞎、點(diǎn)擊測(cè)試導(dǎo)航器中的箭頭按鈕逢唤。

點(diǎn)擊測(cè)試導(dǎo)航器中的箭頭按鈕.png

3拉讯、點(diǎn)擊中縫上的鉆石圖標(biāo)。

4鳖藕、你還可以點(diǎn)擊某個(gè)測(cè)試方法上的鉆石按鈕單獨(dú)測(cè)試這個(gè)方法魔慷,鉆石按鈕在導(dǎo)航器和中縫上都有。

三著恩、怎么查看覆蓋率

iOS UnitTest單元測(cè)試覆蓋率(Code Coverage)

默認(rèn)情況下是不會(huì)顯示覆蓋率的

設(shè)置顯示覆蓋率前后的對(duì)比圖

設(shè)置顯示覆蓋率前后的對(duì)比圖.png

那怎么顯示覆蓋率呢院尔?方法如下圖:

iOS UnitTest單元測(cè)試覆蓋率(Code Coverage).png

四、測(cè)試類怎么編寫(xiě)(一喉誊、Test)

測(cè)試方法的名字總是以 test 開(kāi)頭邀摆,后面加上一個(gè)對(duì)測(cè)試內(nèi)容的描述。

將測(cè)試方法分成 given裹驰、when 和 then 三個(gè)部分是一種好的做法:

在 given 節(jié)隧熙,應(yīng)該給出要計(jì)算的值。
在 when 節(jié)幻林,執(zhí)行要測(cè)試的代碼。
在 then 節(jié)音念,將結(jié)果和你期望的值進(jìn)行斷言沪饺,如果測(cè)試失敗,打印指定的消息闷愤。
點(diǎn)擊中縫上或者測(cè)試導(dǎo)航器上的鉆石圖標(biāo)整葡。App 會(huì)編譯并運(yùn)行,鉆石圖標(biāo)會(huì)變成綠色的對(duì)勾讥脐!

注意:Given-When-Then 結(jié)構(gòu)源自 BDD(行為驅(qū)動(dòng)開(kāi)發(fā))遭居,是一個(gè)對(duì)客戶端友好的、更少專業(yè)術(shù)語(yǔ)的叫法旬渠。另外也可以叫做 Arrange-Act-Assert 和 Assemble-Activate-Assert俱萍。

1、什么叫脫離UI做單元測(cè)試

如果你要測(cè)試方法是寫(xiě)在view上告丢,那么你單元測(cè)試的時(shí)候枪蘑,就不可避免的需要引入這個(gè)view。

以你把你把一個(gè)獲取初始登錄賬號(hào)的方法寫(xiě)在了LoginViewController為例岖免。

#import "LoginViewController.m"

- (NSString *)getLastLoginUserName {
     return @"Beyond";
}

那么你的單元測(cè)試必須就會(huì)有如下view的引入岳颇。這就叫無(wú)法脫離view做單元測(cè)試。

//每個(gè)test方法執(zhí)行之前調(diào)用颅湘,在此方法中可以定義一些全局屬性话侧,類似controller中的viewdidload方法。
- (void)setUp {
    [super setUp];
    // Put setup code here. This method is called before the invocation of each test method in the class.
     self.loginViewController = [[LoginViewController alloc] init];
}

//每個(gè)test方法執(zhí)行之后調(diào)用闯参,釋放測(cè)試用例的資源代碼瞻鹏,這個(gè)方法會(huì)每個(gè)測(cè)試用例執(zhí)行后調(diào)用悲立。
- (void)tearDown {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
    [super tearDown];
    self.loginViewController = nil;
}

//測(cè)試用例的例子,注意測(cè)試用例一定要test開(kāi)頭
- (void)testGetLastLoginUserName {
    NSString *lastLoginUserName = [self.loginViewContoller getLastLoginUserName];
    XCTAssertEqual(lastLoginUserName, @"Beyond",  @"上次登錄賬號(hào)不是Beyond");
}

那么怎么才能做到脫離view層做單元測(cè)試呢乙漓?
答:你可以將該方法寫(xiě)在胖Model中级历,或?qū)懺贖elper中,或?qū)懺赩iewModel中叭披。

五寥殖、測(cè)試類怎么編寫(xiě)(二、UITest)

①涩蜘、新建類
②嚼贡、聲明方法:一定要以test開(kāi)頭
③、將光標(biāo)放在自定義的測(cè)試方法中同诫,錄制宏按鈕變成紅色粤策,點(diǎn)擊它,程序就會(huì)自動(dòng)啟動(dòng)误窖,這時(shí)候在程序中所有的操作都會(huì)生成相應(yīng)的代碼叮盘,并將代碼放到所選的測(cè)試方法體內(nèi)。

注意:錄制的代碼不一定正確霹俺,需要自己調(diào)整柔吼,
如:
app.tables.staticTexts[@"\U5bf9\U8c61”],需要將@"\U5bf9\U8c61”改成對(duì)應(yīng)的中文丙唧,不然測(cè)試運(yùn)行的時(shí)候會(huì)因匹配不了而報(bào)錯(cuò)愈魏。

六、UITest例子

附:例子1登錄

- (void)testLogin {
    XCUIApplication *app = [[XCUIApplication alloc] init];

    if (app.navigationBars.count) {
        XCUIElement *navigationBar = [app.navigationBars elementBoundByIndex:0];

        XCUIElement *mainMineButton = navigationBar.buttons[@"main mine"];
        XCUIElement *mainMessageButton = navigationBar.buttons[@"main message"];
        BOOL isMainViewController = [mainMineButton exists] && [mainMessageButton exists];
        if (isMainViewController) {
            [mainMineButton tap];
            
            XCUIElement *button = [[app.tables containingType:XCUIElementTypeImage identifier:@"mine_arrow_right"] childrenMatchingType:XCUIElementTypeButton].element;
            [button tap];
            
            // 進(jìn)入個(gè)人中心了
            XCUIElement *logoutButton = app.buttons[@"退出登錄"];
            [logoutButton tap];
            
            //XCUIElement *logoutCancelButton = app.buttons[@"取消"];
            //[logoutCancelButton tap];
            //[logoutButton tap];
            
            XCUIElement *logoutOKButton = app.buttons[@"確定"];
            [logoutOKButton tap];
            
            sleep(2);
        }
    }
    
    // 設(shè)置用戶名
    XCUIElement *userNameTextField = app.textFields[@"用戶名"];
    [userNameTextField tap];
    if (userNameTextField.value) {
        NSLog(@"清空初始用戶名:%@", userNameTextField.value);
        XCUIElement *userNameClearTextButton = userNameTextField.buttons[@"Clear text"];
        [userNameClearTextButton tap];
    }
    [userNameTextField typeText:@"Beyond"];
    
    // 設(shè)置密碼
    XCUIElement *passwordTextField = app.secureTextFields[@"密碼"];
    [passwordTextField tap];
    [passwordTextField typeText:@"Pass1234"];
    
    BOOL loginCondition = userNameTextField.isSelected && passwordTextField.isSelected;
    XCTAssertTrue(loginCondition == NO, @"遇到問(wèn)題了想际,檢測(cè)不通過(guò)");
    
    XCUIElement *loginButton = app.buttons[@"登錄"];
    [loginButton tap];
//    for (NSInteger i = 0; i < 5; i++) {
//        [loginButton tap];
//    }
    
    //sleep(5);
    XCTAssertTrue([self isMainViewController:app], @"成功登錄首頁(yè)");
}


- (BOOL)isMainViewController:(XCUIApplication *)app {
    if (app.navigationBars.count) {
        XCUIElement *navigationBar = [app.navigationBars elementBoundByIndex:0];
        
        XCUIElement *mainMineButton = navigationBar.buttons[@"main mine"];
        XCUIElement *mainMessageButton = navigationBar.buttons[@"main message"];
        BOOL isMainViewController = [mainMineButton exists] && [mainMessageButton exists];
        return isMainViewController;
        
    } else {
        return NO;
    }
}

知識(shí)點(diǎn):
//在當(dāng)前頁(yè)面尋找與“用戶名”有關(guān)系的輸入框
XCUIElement *userNameTextField = app.textFields[@"用戶名"];

//獲取焦點(diǎn)成為第一響應(yīng)者培漏,否則會(huì)報(bào)“元素(此textField)未調(diào)起鍵盤(pán)”錯(cuò)誤
[userNameTextField tap];

//獲取文本框的值
NSLog(@"初始用戶名:%@", userNameTextField.value);

//為此textField鍵入字符串
[userNameTextField typeText:@"Beyond"];

附:例子2列表(下拉刷新上拉加載等)

#import "STDemoUITestCase.h"

@interface STDemoOrderUITests : STDemoUITestCase {
    
}
@property (nonatomic, strong) XCUIApplication *app;
@property (nonatomic, strong) XCUIElement *todoStaticText;
@property (nonatomic, strong) XCUIElement *doingStaticText;
@property (nonatomic, strong) XCUIElement *doneStaticText;

@end

@implementation STDemoOrderUITests

- (void)setUp {
    // Put setup code here. This method is called before the invocation of each test method in the class.
    
    // In UI tests it is usually best to stop immediately when a failure occurs.
    self.continueAfterFailure = NO;

    // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
    XCUIApplication *app = [[XCUIApplication alloc] init];
    [app launch];
    self.app = app;
    
    // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    XCUIElement *segmentScrollView = nil;
    for (NSInteger i = 0; i < app.scrollViews.count; i++) {
        XCUIElement *scrollView = [app.scrollViews elementBoundByIndex:i];
        if (scrollView.staticTexts.count == 3) {
            segmentScrollView = scrollView;
            break;
        }
    }
    XCTAssertNotNil(segmentScrollView);
    
    XCUIElement *todoStaticText = [segmentScrollView.staticTexts elementBoundByIndex:0];
    XCUIElement *doingStaticText = [segmentScrollView.staticTexts elementBoundByIndex:1];
    XCUIElement *doneStaticText = [segmentScrollView.staticTexts elementBoundByIndex:2];
    self.todoStaticText = todoStaticText;
    self.doingStaticText = doingStaticText;
    self.doneStaticText = doneStaticText;
}

- (void)changeSegmentIndex:(NSInteger)segmentIndex {
    
}

- (void)testOrderRefresh {
    XCUIApplication *app = self.app;
    XCUIElement *todoStaticText = self.todoStaticText;
    XCUIElement *doingStaticText = self.doingStaticText;
    XCUIElement *doneStaticText = self.doneStaticText;
    
    [todoStaticText tap];
    [doingStaticText tap];
    [doneStaticText tap];
    sleep(2);
    
    [todoStaticText tap];
    XCUIElement *table1 = [app.tables elementBoundByIndex:0];
    [table1 swipeDown];
    [table1 swipeDown];
    [table1 swipeDown];
    [table1 swipeUp];
    sleep(2);

    [table1 swipeLeft];
    sleep(2);

    [table1 swipeDown];
    [table1 swipeUp];
    sleep(2);
    
}

@end

七、定位元素

先說(shuō)明本節(jié)包含知識(shí)點(diǎn)有如下大三點(diǎn):
1胡本、UITest類名介紹
2牌柄、元素獲取方法
3、定位元素

要知道怎么定位元素和元素操作前打瘪,我們先了解以下一些元素的基本概念友鼻。

1、UITest類名介紹

XCTest一共提供了三種UI測(cè)試對(duì)象

①闺骚、XCUIApplication 當(dāng)前測(cè)試應(yīng)用target
②彩扔、XCUIElementQuery 定位查詢當(dāng)前UI中xctuielement的一個(gè)類
③、XCUIElement UI測(cè)試中任何一個(gè)item項(xiàng)都被抽象成一個(gè)XCUIElement類型

1.1僻爽、app元素

XCUIApplication *app = [[XCUIApplication alloc] init];
這里的app獲取的元素虫碉,都是當(dāng)前界面的元素。

app將界面的元素按類型存儲(chǔ)胸梆,在集合中的元素敦捧,元素之間是平級(jí)關(guān)系的须板,按照界面順序從上往下依次排序(這點(diǎn)很重要,有時(shí)很管用)兢卵;元素有子集习瑰,即如一個(gè)大的view包含了多個(gè)子控件。常見(jiàn)的元素有:staticTexts(label)秽荤、textFields(輸入框)甜奄、buttons(按鈕)等等

在Tests中如下代碼有效窃款,在UITests中课兄,如下代碼無(wú)效

    UIViewController *rootViewController = UIApplication.sharedApplication.keyWindow.rootViewController;
    UIViewController *viewController = [UIViewControllerCJHelper findCurrentShowingViewController];
1.2、元素集合(元素下面還是有元素集合)

XCUIApplication* app = [[XCUIApplicationalloc] init];

//獲得當(dāng)前界面中的表視圖

XCUIElement* tableView = [app.tables elementBoundByIndex:0];
XCUIElement* cell = [tableView.cells elementBoundByIndex:0];

//元素下面還是有元素集合晨继,如cell.staticTexts

XCTAssert(cell.staticTexts[@"Welcome"].exists);

1.3烟阐、界面事件

自動(dòng)化測(cè)試無(wú)非就是:輸入框、label賦值紊扬,按鈕的點(diǎn)擊蜒茄、雙擊,頁(yè)面的滾動(dòng)等事件

  • 1.3.1餐屎、點(diǎn)擊事件tap

[app.buttons[@"確認(rèn)"] tap];

  • 1.3.2扩淀、輸入框的賦值

[[app.textFields elementBoundByIndex:i] typeText:@“張三"];

當(dāng)測(cè)試方法執(zhí)行結(jié)束后,模擬器的界面就進(jìn)入后臺(tái)了啤挎,為了不讓它進(jìn)入后臺(tái),可以在方法結(jié)尾處下一個(gè)斷點(diǎn)卵凑。這時(shí)候的app正在運(yùn)行中庆聘,只要這個(gè)測(cè)試方法沒(méi)有結(jié)束,我們可以進(jìn)行別的操作的(不一定就要按照代碼來(lái)執(zhí)行)勺卢。

2伙判、元素獲取方法

@property (nonatomic, strong) XCUIApplication *app;

2.1 順序獲取
    // 方法①、按順序黑忱,適合identify變化宴抚,一般我們采用這種方法
    XCUIElement *todoStaticText = [segmentScrollView.staticTexts elementBoundByIndex:0];
    XCUIElement *doingStaticText = [segmentScrollView.staticTexts elementBoundByIndex:1];
    XCUIElement *doneStaticText = [segmentScrollView.staticTexts elementBoundByIndex:2];

順序獲取以下兩種方法是等價(jià)的
XCUIElementQuery *navigationBarItems = navigationBar.buttons;
XCUIElementQuery *navigationBarItems = [navigationBar childrenMatchingType:XCUIElementTypeButton];

XCUIElement *navigationBar = [self.app.navigationBars elementBoundByIndex:0];
XCUIElement *navigationBar = self.app.navigationBars.allElemenstBoundByIndex[0];

2.2 identify 獲取

    // 方法②、按id甫煞,當(dāng)標(biāo)簽不變的情況下
    XCUIElement *todoStaticText = segmentScrollView.staticTexts[@"待配送"];
    XCUIElement *doingStaticText = segmentScrollView.staticTexts[@"配送中"];
    XCUIElement *doneStaticText = segmentScrollView.staticTexts[@"已配送"];

3菇曲、定位元素

要獲取到元素,我們的前提是要定位到元素的層次抚吠。

3.1 意識(shí)定位
3.1.1 獲取 app
- (void)setUp {
    // Put setup code here. This method is called before the invocation of each test method in the class.
    
    // In UI tests it is usually best to stop immediately when a failure occurs.
    self.continueAfterFailure = NO;

    // UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method.
    XCUIApplication *app = [[XCUIApplication alloc] init];
    [app launch];
    self.app = app;
    
    // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.

3.1.2 如何獲取 UITabBarController 的 Item
    NSArray<XCUIElement *> *tabBars = self.app.tabBars.allElementsBoundByIndex;
    XCUIElement *tabBar = tabBars[0];
    XCUIElementQuery *tabBarItems = [tabBar childrenMatchingType:XCUIElementTypeButton];
    XCUIElement *tabBarItem1 = [tabBarItems elementBoundByIndex:0];
    XCUIElement *tabBarItem2 = [tabBarItems elementBoundByIndex:1];
    XCUIElement *tabBarItem3 = [tabBarItems elementBoundByIndex:2];
    XCUIElement *tabBarItem4 = [tabBarItems elementBoundByIndex:3];
    [tabBarItem1 tap];
    [tabBarItem2 tap];
    [tabBarItem3 tap];
    [tabBarItem4 tap];
3.1.3 如何獲取 UISegmentControl 的 label
    XCUIElement *segmentScrollView = nil;
    for (NSInteger i = 0; i < app.scrollViews.count; i++) {
        XCUIElement *scrollView = [app.scrollViews elementBoundByIndex:i];
        if (scrollView.staticTexts.count == 3) {
            segmentScrollView = scrollView;
            break;
        }
    }
    XCTAssertNotNil(segmentScrollView);
    
    XCUIElement *todoStaticText = [segmentScrollView.staticTexts elementBoundByIndex:0];
    XCUIElement *doingStaticText = [segmentScrollView.staticTexts elementBoundByIndex:1];
    XCUIElement *doneStaticText = [segmentScrollView.staticTexts elementBoundByIndex:2];
    self.todoStaticText = todoStaticText;
    self.doingStaticText = doingStaticText;
    self.doneStaticText = doneStaticText;

    [self.todoStaticText tap];
    [self.doingStaticText tap];
    [self.doneStaticText tap];
3.1.4 如何獲取導(dǎo)航欄及其上的按鈕
    XCUIElement *navigationBar = [self.app.navigationBars elementBoundByIndex:0];
    
    XCUIElement *mainMineButton = navigationBar.buttons[@"main mine"];
    [mainMineButton tap];

    XCUIElementQuery *navigationBarItems = navigationBar.buttons;
    //XCUIElementQuery *navigationBarItems = [navigationBar childrenMatchingType:XCUIElementTypeButton];
    XCUIElement *backButton = [navigationBarItems elementBoundByIndex:0];
3.1.5 如何 label
    // 單擊 label
    XCUIElement *tapStaticText = self.app.staticTexts[@"單擊"];
    [tapStaticText tap];

    XCUIElement *todoStaticText = segmentScrollView.staticTexts[@"待配送"];
3.1.6 如何獲取 button
    // 單擊 button
    XCUIElement *tapButton = self.app.buttons[@"確定"];
    [tapButton tap];
3.1.7 如何獲取 textField 及 其上的值
XCUIElement *userNameTextField = self.app.textFields[@"用戶名"];
NSLog(@"用戶名:%@", userNameTextField.value);
3.1.8 如何獲取 textField 的 刪除鍵
    // 設(shè)置用戶名
    XCUIElement *userNameTextField = self.app.textFields[@"用戶名"];
    [userNameTextField tap];
    if (userNameTextField.value) {
        NSLog(@"清空初始用戶名:%@", userNameTextField.value);
        XCUIElement *userNameClearTextButton = userNameTextField.buttons[@"Clear text"];
        [userNameClearTextButton tap];
    }
    [userNameTextField typeText:userName];
3.1.9 如何獲取 keyboard 的 return 鍵
    // 鍵盤(pán)
    XCUIElement *keyboard = [self.app.keyboards elementBoundByIndex:0];
    // 鍵盤(pán) search 鍵
    XCUIElement *keyboardSerch = keyboard.buttons[@"Search"];
3.2 調(diào)試定位

以一個(gè)標(biāo)著"1"到"5"標(biāo)簽五個(gè)單元的表為例常潮。如下圖:

一個(gè)標(biāo)著"1"到"5"標(biāo)簽五個(gè)單元的表.png

當(dāng)觸摸帶有標(biāo)簽"3"的單元時(shí)候你可以打印如下的日志(為了清晰顯示,這里忽略一些關(guān)鍵字輸出):

(lldb) po app.tables.element.cells[@"Three"]

Query chain:
 →Find: Target Application
  ??Find: Descendants matching type Table
    Input: {
      Application:{ {0.0, 0.0}, {375.0, 667.0} }, label: "Demo"
    }
    Output: {
      Table: { {0.0, 0.0}, {375.0, 667.0} }
    }
    ??Find: Descendants matching type Cell
      Input: {
        Table: { {0.0, 0.0}, {375.0, 667.0} }
      }
      Output: {
        Cell: { {0.0, 64.0}, {375.0, 44.0} }, label: "One"
        Cell: { {0.0, 108.0}, {375.0, 44.0} }, label: "Two"
        Cell: { {0.0, 152.0}, {375.0, 44.0} }, label: "Three"
        Cell: { {0.0, 196.0}, {375.0, 44.0} }, label: "Four"
        Cell: { {0.0, 240.0}, {375.0, 44.0} }, label: "Five"
      }
      ??Find: Elements matching predicate ""Three" IN identifiers"
        Input: {
          Cell: { {0.0, 64.0}, {375.0, 44.0} }, label: "One"
          Cell: { {0.0, 108.0}, {375.0, 44.0} }, label: "Two"
          Cell: { {0.0, 152.0}, {375.0, 44.0} }, label: "Three"
          Cell: { {0.0, 196.0}, {375.0, 44.0} }, label: "Four"
          Cell: { {0.0, 240.0}, {375.0, 44.0} }, label: "Five"
        }
        Output: {
          Cell: { {0.0, 152.0}, {375.0, 44.0} }, label: "Three"
        }

觀察輸出結(jié)果:在第一個(gè)輸入/輸出循環(huán)中的 -table 方法返回了填充在這個(gè) iphone6 模擬器屏幕里面的列表(table)楷力。再往下就是 -cells 方法返回了所有的單元(cell)喊式。最終孵户,文本查詢僅僅在最后返回了一個(gè)元素。如果你沒(méi)有在輸出的最后看到帶"Output"關(guān)鍵字的輸出岔留,說(shuō)明框架沒(méi)有找到你想要的元素夏哭。

3.3 認(rèn)識(shí)控件的identifier及如何設(shè)置

如果控件是 UILabel 、UITextFiled 或者 UIButton 等可以設(shè)置 text 的控件献联,那么其 identifier 就是 text竖配。

其實(shí)不管控件是否可以設(shè)置 text,都是可以通過(guò) accessibilityIdentifier 設(shè)置的酱固。

UILabel *userNameLabel = [[UILabel alloc] initWithFrame:CGRectZero];
userNameLabel.text = @"張三";
userNameLabel.accessibilityIdentifier = @"userNameLabel";

則userNameLabel的identifier就由本來(lái)的text值"張三",變成了accessibilityIdentifier值"userNameLabel";

identifier 最好設(shè)置成英文械念,中文的話會(huì)被轉(zhuǎn)碼,不好找T吮A浼酢!

設(shè)置完accessibilityIdentifier后班眯,怎么通過(guò)accessibilityIdentifier找到要找的控件希停。答,可以通過(guò)打印allElementsBoundByAccessibilityElement值署隘。

NSLog(@"GS: tabBars%@",_app.tabBars.allElementsBoundByAccessibilityElement);
NSLog(@"GS: segmentedControls%@",_app.segmentedControls.allElementsBoundByAccessibilityElement);

八宠能、元素操作

1、點(diǎn)擊

太簡(jiǎn)單了磁餐,略

2违崇、視圖變化

①剛開(kāi)始是什么都沒(méi)處理,直接干诊霹;
②后來(lái)發(fā)現(xiàn)明明OK的羞延,卻測(cè)試不通過(guò);然后就臨時(shí)采用了sleep脾还;
③再后來(lái)終于找到了精確判斷的方法伴箩。如同單元測(cè)試的異步處理一樣;

剛開(kāi)始最想想到的是sleep鄙漏,但是sleep短嗤谚,還是無(wú)效。而sleep長(zhǎng)怔蚌,則必然造成每個(gè)自動(dòng)化測(cè)試所消耗的時(shí)間延長(zhǎng)巩步,而且還不一定就都OK。所以最后的方法如下:

// "STDemoUITestCase.h"
@interface STDemoUITestCase : XCTestCase {
    
}
- (void)waitElement:(XCUIElement *)element untilVisible:(BOOL)visible;

@end

@implementation STDemoUITestCase

- (void)waitElement:(XCUIElement *)element untilVisible:(BOOL)visible {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"exists == %ld", visible ? 1 : 0];
    [self expectationForPredicate:predicate evaluatedWithObject:element handler:nil];
    [self waitForExpectationsWithTimeout:30 handler:^(NSError * _Nullable error) {
        //NSString *message = @"Failed to find \(element) after 30 seconds.";
        //[self recordFailureWithDescription:message inFile:__FILE__ atLine:__LINE__ expected:YES];
    }];
}

@end

所以最終的判斷方法如下:

- (void)testWaitViewVisible {
    XCUIElement *passwordTextField = self.app.secureTextFields[@"密碼"];
    [self waitElement:passwordTextField untilVisible:YES];
}

附:在測(cè)試這個(gè)異步方法的時(shí)候媚创,遇到過(guò)一個(gè)奇怪的問(wèn)題渗钉。原來(lái)的測(cè)試代碼如下:

- (void) testWaitViewVisible {
    XCUIElement *passwordTextField = self.app.secureTextFields[@"密碼"];
    
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"exists == 1"];//正確空格
    
    [self expectationForPredicate:predicate evaluatedWithObject:passwordTextField handler:nil];
    [self waitForExpectationsWithTimeout:2.0 handler:nil];
}

不知道為什么應(yīng)該測(cè)試通過(guò)的,卻一直在執(zhí)行到NSPredicate *predicate = [NSPredicate predicateWithFormat:@"exists的時(shí)候就崩潰了。百思不得其解鳄橘。后來(lái)通過(guò)復(fù)制代碼及search才發(fā)現(xiàn)是如下圖所示問(wèn)題声离。

NSPredicate謂詞定義失敗.png

即原來(lái)是空格不是英文的空格。害我一直在懷疑是不是自己寫(xiě)的語(yǔ)法有問(wèn)題瘫怜。

其他屬性判斷請(qǐng)認(rèn)真查看XCUIElement類及屬性和方法的英文注釋术徊。
如判斷登錄button是否enable。

- (void)waitElement:(XCUIElement *)element untilEnable:(BOOL)enable {
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"hittable == %ld", enable ? 1 : 0];
    [self expectationForPredicate:predicate evaluatedWithObject:element handler:nil];
    [self waitForExpectationsWithTimeout:3 handler:^(NSError * _Nullable error) {
        //NSString *message = @"Failed to find \(element) after 30 seconds.";
        //[self recordFailureWithDescription:message inFile:__FILE__ atLine:__LINE__ expected:YES];
    }];
}

附2:以下幾個(gè)別人也遇到的異步處理的文章鲸湃,處理方式和本文所講一樣赠涮。可略過(guò)

附3:UI Testing in Xcode 7這是一篇從上述Delay/Wait in a test case of Xcode UI testing的問(wèn)題暗挑,別人的回答中笋除,找到的一篇UITest的文章。寫(xiě)得很不錯(cuò)炸裆,很全垃它,建議看。

其他參考文章

其他參考文章1烹看、看過(guò)本文的可略過(guò)国拇,因?yàn)槟沁叺臇|西本文都提過(guò)
iOS UITests(UI自動(dòng)化測(cè)試 實(shí)現(xiàn))

其他參考文章2、看過(guò)本文的再看惯殊,因?yàn)槟沁呌行〇|西本文沒(méi)提
iOS單元測(cè)試和UI測(cè)試
iOS自動(dòng)化測(cè)試的那些干貨

其他參考文章3酱吝、本文未涉及的知識(shí)點(diǎn)
iOS 無(wú)限monkey測(cè)試解決方案
iOS客戶端monkey測(cè)試
iOS自動(dòng)化測(cè)試框架對(duì)比

九、WebDriverAgent的使用

在進(jìn)行下節(jié)《使用Appium進(jìn)行iOS的自動(dòng)化測(cè)試》前土思,我們先了解WebDriverAgent的使用务热,因?yàn)椤妒褂肁ppium進(jìn)行iOS的自動(dòng)化測(cè)試》中需要替換Appium中的WebDriverAgent;

先講下模擬器下的使用:
1己儒、到WebDriverAgent下載最新版本的WebDriverAgent
2陕习、進(jìn)入下載后的WebDriverAgent文件
3、執(zhí)行 ./Scripts/bootstrap.sh
4址愿、直接用Xcode打開(kāi)WebDriverAgent.xcodepro文件
5、連接并選擇自己的iOS設(shè)備冻璃,然后按Cmd+U响谓,或是點(diǎn)擊Product->Test
6、運(yùn)行成功時(shí)省艳,在Xcode控制臺(tái)應(yīng)該可以打印出一個(gè)Ip地址和端口號(hào)娘纷。
7、在網(wǎng)址上輸入http://(iP地址):(端口號(hào))/status跋炕,如果網(wǎng)頁(yè)顯示了一些json格式的數(shù)據(jù)赖晶,說(shuō)明運(yùn)行成功。

如果真機(jī)的話,還需要配置配置WebDriverAgentLib和WebDriverAgentRunner的證書(shū)遏插。

十捂贿、使用Appium進(jìn)行自動(dòng)化測(cè)試

需要
1、安裝Appium-Desktop
2胳嘲、安裝appium-doctor
3厂僧、更新Appium中的WebDriverAgent
4、安裝Appium-Python-Client

2了牛、appium-doctor的安裝

2.1颜屠、檢查是否安裝appium-doctor是否安裝了,以及與iOS相關(guān)配置是否完整

執(zhí)行appium-doctor --ios指令鹰祸,查看appium-doctor的安裝甫窟,以及與iOS相關(guān)配置是否完整。如下圖蛙婴,執(zhí)行后發(fā)現(xiàn)未找到命令即未安裝粗井。

appium-doctor --ios.png
2.2、未安裝appium-doctor時(shí)敬锐,進(jìn)行安裝

則我們需要執(zhí)行sudo npm install appium-doctor -g來(lái)進(jìn)行appium-doctor的安裝

sudo npm install appium-doctor -g.png

附:如果你忘了添加sudo,只是執(zhí)行npm install appium-doctor -g的話背传,會(huì)出現(xiàn)如下錯(cuò)誤
npm install appium-doctor -g.png

2.3、安裝后台夺,檢查是否是否真的安裝了以及與iOS相關(guān)配置是否完整

appium-doctor安裝后径玖,我們?cè)賵?zhí)行appium-doctor --ios指令,查看appium-doctor是否真的安裝了颤介,以及與iOS相關(guān)配置是否完整梳星。如果有那一項(xiàng)是打叉的,則進(jìn)行安裝就可以了滚朵。如下圖發(fā)現(xiàn)Xcode Command Line Tools未安裝冤灾。

image.png

則我們Fix it選擇YES,發(fā)現(xiàn)還是一樣的問(wèn)題辕近,就自己執(zhí)行xcode-select --install進(jìn)行安裝韵吨。
控制臺(tái)執(zhí)行xcode-select --install,在彈出的彈框中選擇“安裝”移宅,即可進(jìn)入下載和安裝了归粉,安裝過(guò)程如下圖:

xcode-select --install安裝過(guò)程中.png

安裝成功后,再執(zhí)行xcode-select --install其會(huì)提示我們已經(jīng)安裝了漏峰。同時(shí)如果執(zhí)行sudo npm install appium-doctor -g其也會(huì)告訴我們appium-doctor與iOS的相關(guān)配置也安裝成功了糠悼。
xcode-select --install安裝成功后

3、更新Appium中的WebDriverAgent

進(jìn)入到Appium中的WebDriverAgent目錄/Applications/Appium.app/Contents/Resources/app/node_modules/appium/node_modules/appium-xcuitest-driver/浅乔,將自己下載并編譯后的WebDriverAgent替換Appium原有的WebDriverAgent

4倔喂、安裝python

因?yàn)槲覀兒竺媸怯胮y腳本文件執(zhí)行自動(dòng)化測(cè)試,所以需要安裝python。

執(zhí)行python --version檢查python是否安裝席噩,如果未安裝請(qǐng)執(zhí)行brew install python安裝

檢查python是否安裝.png

5班缰、安裝Appium-Python-Client

因?yàn)槲覀兊膒y腳本文件中有from appium import webdriver

from appium import webdriver.png

所以,我們需要安裝Appium-Python-Client班挖。如果未安裝就去執(zhí)行py文件鲁捏,則會(huì)出現(xiàn)ImportError: No module named appium錯(cuò)誤,如下圖:

ImportError: No module named appium.png

所以萧芙,請(qǐng)確保在執(zhí)行py腳本文件前给梅,你的

5.1、下載python-client源碼Appium-Python-Client是安裝了的双揪。
cd /Users/lichaoqian/Desktop
git clone git@github.com:appium/python-client.git
下載python-client源碼.png
5.2动羽、安裝python-client
cd python-client/
sudo python setup.py install

不要加了加sudo

安裝python-client命令.png

執(zhí)行成功如圖:


安裝python-client成功.png

6、執(zhí)行腳本

執(zhí)行python appiumSimpleDemo.py遇到的問(wèn)題:

Unknown device or simulator UDID.png

原因是沒(méi)有安裝 libimobiledevice渔期,導(dǎo)致Appium無(wú)法連接到iOS的設(shè)備运吓。
在介紹怎么安裝libimobiledevice前,我們先看看若安裝好libimobiledevice后疯趟,其執(zhí)行的結(jié)果又是什么拘哨?截圖如下:

在此之前,我還遇到的問(wèn)題有ImportError: cannot import name _remove_dead_weakref,如下截圖:

image.png

這里我原以為只要執(zhí)行``更新p就可以了信峻,即:
brew install python更新.png

如果其已經(jīng)是最新版本倦青,則其提示如下:
brew install python已經(jīng)是最新.png

這時(shí)候我們?nèi)?zhí)行總不會(huì)報(bào)那個(gè)表示python版本的問(wèn)題了吧。然而盹舞,實(shí)際上它的結(jié)果還是和之前一樣产镐,可是我們明明已經(jīng)安裝了最新的python了,為什么還是錯(cuò)誤踢步,到底問(wèn)題出在哪里癣亚。經(jīng)過(guò)一番摸索,才發(fā)現(xiàn)原來(lái)它執(zhí)行的是python@2获印,而不是python述雾,所以我就嘗試著要不先去刪掉python@2看看是錯(cuò)誤吧。刪除的命令如下:
brew uninstall --ignore-dependencies python@2
brew uninstall --ignore-dependencies python@2.png

刪除成功后兼丰,再執(zhí)行一遍python appiumSimpleDemo.py命令绰咽。這時(shí)候的結(jié)果變?yōu)槿缦拢?br>
image.png

可以看到這時(shí)候它調(diào)用的就是python命令,而不是python@2了地粪。
解決了python后,這時(shí)候還有另一個(gè)問(wèn)題琐谤,即圖上的Original error: Could not initialize ios-deploy make sure it is installed (npm install -g ios-deploy) and works on your system.蟆技。它的意思就是缺少了ios-deploy。
為什么需要ios-deploy呢?因?yàn)槿绻覀円趇OS10+的系統(tǒng)上使用appium质礼,則需要安裝ios-deploy旺聚。
顯然我們肯定需要在iOS10+的系統(tǒng)上使用appium,所以我們根據(jù)它的提示npm install -g ios-deploy去安裝ios-deploy即可(不要高興得太早)眶蕉。然而它提供的命令并不能完全讓我們安裝成功砰粹。如下圖:
image.png

你肯定猜到了是sudo的問(wèn)題吧,不過(guò)這里比較特殊造挽,就是即使你加上sudo碱璃,即執(zhí)行的是sudo npm install -g ios-deploy也還是無(wú)法成功。那正確的完整的命令應(yīng)該是怎么樣的呢饭入?答:這個(gè)問(wèn)題的解決方法在
https://github.com/phonegap/ios-deploy/issues/188中可以找到嵌器,其實(shí)就是sudo npm install -g ios-deploy --unsafe-perm=true。執(zhí)行后谐丢,如下圖所示:
sudo npm install -g ios-deploy --unsafe-perm=true.png

好了爽航,解決了這個(gè)問(wèn)題后,我們?cè)倩仡^來(lái)執(zhí)行下py腳本乾忱,看看還有什么問(wèn)題沒(méi)讥珍。
執(zhí)行如下,
成功執(zhí)行py腳本.png

從圖上可以看出窄瘟,我們終于成功了衷佃。。寞肖。是的纲酗,你成功了。而且你看你的手機(jī)新蟆,你會(huì)發(fā)現(xiàn)在這個(gè)腳本的執(zhí)行過(guò)程中觅赊,你的手機(jī)是在自動(dòng)化測(cè)試的。

6.1琼稻、libimobiledevice的安裝

執(zhí)行brew install libimobiledevice --HEAD命令吮螺,進(jìn)行l(wèi)ibimobiledevice的安裝。

libimobiledevice的安裝1.png

根據(jù)錯(cuò)誤提示帕翻,我們執(zhí)行在終端繼續(xù)sudo chown -R $(whoami) /usr/local/share/man/man3 /usr/local/share/man/man5 /usr/local/share/man/man7命令鸠补。執(zhí)行成功后,回頭執(zhí)行之前執(zhí)行沒(méi)成功的brew install libimobiledevice --HEAD命令嘀掸,進(jìn)行l(wèi)ibimobiledevice的安裝紫岩。可以發(fā)現(xiàn)這時(shí)候它就正常安裝了睬塌。如下圖:
image.png

但執(zhí)行過(guò)程中泉蝌,當(dāng)執(zhí)行到./autogen.sh的時(shí)候又發(fā)現(xiàn)另外一個(gè)問(wèn)題歇万,如下圖:
libusbmuxd.png

這又是什么原因呢?(PS:Requested 'libusbmuxd >= 1.1.0' but version of libusbmuxd is 1.0.10這個(gè)問(wèn)題勋陪,您可能在用Flutter的時(shí)候也會(huì)遇到贪磺,如果遇到解決方法跟這邊一樣。)

我們仔細(xì)看诅愚,會(huì)發(fā)現(xiàn)異常所在Requested 'libusbmuxd >= 1.1.0' but version of libusbmuxd is 1.0.10寒锚,很顯然是由于系統(tǒng)要求的*libusbmuxd *版本和所要安裝的版本不一致。那怎么解決呢违孝?其實(shí)很簡(jiǎn)單刹前。只要把舊的卸載了,裝個(gè)新的就是了等浊。
卸載命令為:brew uninstall --ignore-dependencies usbmuxd
安裝命令為:brew install --HEAD usbmuxd
如:

image.png

這時(shí)候再去執(zhí)行brew install libimobiledevice --HEAD命令腮郊,成功的截圖如下:
brew install libimobiledevice --HEAD安裝成功.png

其他參考文章:

End

暫時(shí)結(jié)束,后續(xù)會(huì)再補(bǔ)充筹燕。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末轧飞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子撒踪,更是在濱河造成了極大的恐慌过咬,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件制妄,死亡現(xiàn)場(chǎng)離奇詭異掸绞,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)耕捞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)衔掸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人俺抽,你說(shuō)我怎么就攤上這事敞映。” “怎么了磷斧?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵振愿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我弛饭,道長(zhǎng)冕末,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任侣颂,我火速辦了婚禮档桃,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘憔晒。我一直安慰自己藻肄,他們只是感情好销凑,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著仅炊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澎蛛。 梳的紋絲不亂的頭發(fā)上抚垄,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音谋逻,去河邊找鬼呆馁。 笑死,一個(gè)胖子當(dāng)著我的面吹牛毁兆,可吹牛的內(nèi)容都是我干的浙滤。 我是一名探鬼主播,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼气堕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼纺腊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起茎芭,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤揖膜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后梅桩,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體壹粟,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年宿百,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了趁仙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡垦页,死狀恐怖雀费,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情外臂,我是刑警寧澤坐儿,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布貌矿,位于F島的核電站赘艳,受9級(jí)特大地震影響菩暗,放射性物質(zhì)發(fā)生泄漏掏熬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一这刷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧定鸟,春花似錦、人聲如沸聊闯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至惕耕,卻和暖如春挤安,著一層夾襖步出監(jiān)牢的瞬間丛肢,已是汗流浹背氢伟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工颠蕴, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掀泳,地道東北人雪隧。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像员舵,于是被迫代替她去往敵國(guó)和親脑沿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

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

  • 最美的年華里马僻,我遇見(jiàn)了你庄拇。 就如同現(xiàn)代作家馮唐的名詩(shī): “春水初升, 春林初盛韭邓, 春風(fēng)十里措近, 不如你∨纾” 高二那年...
    一世福緣閱讀 869評(píng)論 14 21
  • 看過(guò)了多少繁華瞭郑,飲過(guò)了多少風(fēng)霜。 聽(tīng)過(guò)了多少謊話鸭你,撕破了多少虛假屈张。 自己口中的我一直是神話, 別人眼里的我只是個(gè)笑...
    i鹿小妖閱讀 404評(píng)論 15 18