iOS 单元测试及自动化测试(只看这篇就够了)

目录

  • 一、怎么添加测试类
  • 二、怎么运行测试类
  • 三、怎么查看覆盖率
  • 四、测试类怎么编写(一、Test)
  • 五、测试类怎么编写(二、UITest)
  • 六、UITest例子
  • 七、定位元素
    1、UITest类名介绍
    2、元素获取方法
    3、定位元素
  • 八、元素操作
  • 九、WebDriverAgent的使用
  • 十、使用Appium进行自动化测试
    1、安装Appium-Desktop
    2、安装appium-doctor
    3、更新Appium中的WebDriverAgent

前言

单元测试及自动化测试(小白和大神都一定要了解的知识)

一、怎么添加测试类

二、怎么运行测试类

有三种运行这个测试类的方法:
1、Product\Test 或者 Command-U。这实际上会运行所有测试类。

2、点击测试导航器中的箭头按钮。

点击测试导航器中的箭头按钮.png

3、点击中缝上的钻石图标。

4、你还可以点击某个测试方法上的钻石按钮单独测试这个方法,钻石按钮在导航器和中缝上都有。

三、怎么查看覆盖率

iOS UnitTest单元测试覆盖率(Code Coverage)

默认情况下是不会显示覆盖率的

设置显示覆盖率前后的对比图

iOS 单元测试及自动化测试(只看这篇就够了)_第1张图片
设置显示覆盖率前后的对比图.png

那怎么显示覆盖率呢?方法如下图:

iOS 单元测试及自动化测试(只看这篇就够了)_第2张图片
iOS UnitTest单元测试覆盖率(Code Coverage).png

四、测试类怎么编写(一、Test)

测试方法的名字总是以 test 开头,后面加上一个对测试内容的描述。

将测试方法分成 given、when 和 then 三个部分是一种好的做法:

在 given 节,应该给出要计算的值。
在 when 节,执行要测试的代码。
在 then 节,将结果和你期望的值进行断言,如果测试失败,打印指定的消息。
点击中缝上或者测试导航器上的钻石图标。App 会编译并运行,钻石图标会变成绿色的对勾!

注意:Given-When-Then 结构源自 BDD(行为驱动开发),是一个对客户端友好的、更少专业术语的叫法。另外也可以叫做 Arrange-Act-Assert 和 Assemble-Activate-Assert。

1、什么叫脱离UI做单元测试

如果你要测试方法是写在view上,那么你单元测试的时候,就不可避免的需要引入这个view。

以你把你把一个获取初始登录账号的方法写在了LoginViewController为例。

#import "LoginViewController.m"

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

那么你的单元测试必须就会有如下view的引入。这就叫无法脱离view做单元测试。

//每个test方法执行之前调用,在此方法中可以定义一些全局属性,类似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];
}

//每个test方法执行之后调用,释放测试用例的资源代码,这个方法会每个测试用例执行后调用。
- (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;
}

//测试用例的例子,注意测试用例一定要test开头
- (void)testGetLastLoginUserName {
    NSString *lastLoginUserName = [self.loginViewContoller getLastLoginUserName];
    XCTAssertEqual(lastLoginUserName, @"Beyond",  @"上次登录账号不是Beyond");
}

那么怎么才能做到脱离view层做单元测试呢?
答:你可以将该方法写在胖Model中,或写在Helper中,或写在ViewModel中。

五、测试类怎么编写(二、UITest)

①、新建类
②、声明方法:一定要以test开头
③、将光标放在自定义的测试方法中,录制宏按钮变成红色,点击它,程序就会自动启动,这时候在程序中所有的操作都会生成相应的代码,并将代码放到所选的测试方法体内。

注意:录制的代码不一定正确,需要自己调整,
如:
app.tables.staticTexts[@"\U5bf9\U8c61”],需要将@"\U5bf9\U8c61”改成对应的中文,不然测试运行的时候会因匹配不了而报错。

六、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];
            
            // 进入个人中心了
            XCUIElement *logoutButton = app.buttons[@"退出登录"];
            [logoutButton tap];
            
            //XCUIElement *logoutCancelButton = app.buttons[@"取消"];
            //[logoutCancelButton tap];
            //[logoutButton tap];
            
            XCUIElement *logoutOKButton = app.buttons[@"确定"];
            [logoutOKButton tap];
            
            sleep(2);
        }
    }
    
    // 设置用户名
    XCUIElement *userNameTextField = app.textFields[@"用户名"];
    [userNameTextField tap];
    if (userNameTextField.value) {
        NSLog(@"清空初始用户名:%@", userNameTextField.value);
        XCUIElement *userNameClearTextButton = userNameTextField.buttons[@"Clear text"];
        [userNameClearTextButton tap];
    }
    [userNameTextField typeText:@"Beyond"];
    
    // 设置密码
    XCUIElement *passwordTextField = app.secureTextFields[@"密码"];
    [passwordTextField tap];
    [passwordTextField typeText:@"Pass1234"];
    
    BOOL loginCondition = userNameTextField.isSelected && passwordTextField.isSelected;
    XCTAssertTrue(loginCondition == NO, @"遇到问题了,检测不通过");
    
    XCUIElement *loginButton = app.buttons[@"登录"];
    [loginButton tap];
//    for (NSInteger i = 0; i < 5; i++) {
//        [loginButton tap];
//    }
    
    //sleep(5);
    XCTAssertTrue([self isMainViewController:app], @"成功登录首页");
}


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

知识点:
//在当前页面寻找与“用户名”有关系的输入框
XCUIElement *userNameTextField = app.textFields[@"用户名"];

//获取焦点成为第一响应者,否则会报“元素(此textField)未调起键盘”错误
[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

七、定位元素

先说明本节包含知识点有如下大三点:
1、UITest类名介绍
2、元素获取方法
3、定位元素

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

1、UITest类名介绍

XCTest一共提供了三种UI测试对象

①、XCUIApplication 当前测试应用target
②、XCUIElementQuery 定位查询当前UI中xctuielement的一个类
③、XCUIElement UI测试中任何一个item项都被抽象成一个XCUIElement类型

1.1、app元素

XCUIApplication *app = [[XCUIApplication alloc] init];
这里的app获取的元素,都是当前界面的元素。

app将界面的元素按类型存储,在集合中的元素,元素之间是平级关系的,按照界面顺序从上往下依次排序(这点很重要,有时很管用);元素有子集,即如一个大的view包含了多个子控件。常见的元素有:staticTexts(label)、textFields(输入框)、buttons(按钮)等等

在Tests中如下代码有效,在UITests中,如下代码无效

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

XCUIApplication* app = [[XCUIApplicationalloc] init];

//获得当前界面中的表视图

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

//元素下面还是有元素集合,如cell.staticTexts

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

1.3、界面事件

自动化测试无非就是:输入框、label赋值,按钮的点击、双击,页面的滚动等事件

  • 1.3.1、点击事件tap

[app.buttons[@"确认"] tap];

  • 1.3.2、输入框的赋值

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

当测试方法执行结束后,模拟器的界面就进入后台了,为了不让它进入后台,可以在方法结尾处下一个断点。这时候的app正在运行中,只要这个测试方法没有结束,我们可以进行别的操作的(不一定就要按照代码来执行)。

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];

顺序获取以下两种方法是等价的
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,当标签不变的情况下
    XCUIElement *todoStaticText = segmentScrollView.staticTexts[@"待配送"];
    XCUIElement *doingStaticText = segmentScrollView.staticTexts[@"配送中"];
    XCUIElement *doneStaticText = segmentScrollView.staticTexts[@"已配送"];

3、定位元素

要获取到元素,我们的前提是要定位到元素的层次。

3.1 意识定位
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 *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 如何获取导航栏及其上的按钮
    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 的 删除键
    // 设置用户名
    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 键
    // 键盘
    XCUIElement *keyboard = [self.app.keyboards elementBoundByIndex:0];
    // 键盘 search 键
    XCUIElement *keyboardSerch = keyboard.buttons[@"Search"];
3.2 调试定位

以一个标着"1"到"5"标签五个单元的表为例。如下图:

iOS 单元测试及自动化测试(只看这篇就够了)_第3张图片
一个标着"1"到"5"标签五个单元的表.png

当触摸带有标签"3"的单元时候你可以打印如下的日志(为了清晰显示,这里忽略一些关键字输出):

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

观察输出结果:在第一个输入/输出循环中的 -table 方法返回了填充在这个 iphone6 模拟器屏幕里面的列表(table)。再往下就是 -cells 方法返回了所有的单元(cell)。最终,文本查询仅仅在最后返回了一个元素。如果你没有在输出的最后看到带"Output"关键字的输出,说明框架没有找到你想要的元素。

3.3 认识控件的identifier及如何设置

如果控件是 UILabel 、UITextFiled 或者 UIButton 等可以设置 text 的控件,那么其 identifier 就是 text。

其实不管控件是否可以设置 text,都是可以通过 accessibilityIdentifier 设置的。

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

则userNameLabel的identifier就由本来的text值"张三",变成了accessibilityIdentifier值"userNameLabel";

identifier 最好设置成英文,中文的话会被转码,不好找!!!

设置完accessibilityIdentifier后,怎么通过accessibilityIdentifier找到要找的控件。答,可以通过打印allElementsBoundByAccessibilityElement值。

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

八、元素操作

1、点击

太简单了,略

2、视图变化

①刚开始是什么都没处理,直接干;
②后来发现明明OK的,却测试不通过;然后就临时采用了sleep;
③再后来终于找到了精确判断的方法。如同单元测试的异步处理一样;

刚开始最想想到的是sleep,但是sleep短,还是无效。而sleep长,则必然造成每个自动化测试所消耗的时间延长,而且还不一定就都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];
}

附:在测试这个异步方法的时候,遇到过一个奇怪的问题。原来的测试代码如下:

- (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];
}

不知道为什么应该测试通过的,却一直在执行到NSPredicate *predicate = [NSPredicate predicateWithFormat:@"exists的时候就崩溃了。百思不得其解。后来通过复制代码及search才发现是如下图所示问题。

iOS 单元测试及自动化测试(只看这篇就够了)_第4张图片
NSPredicate谓词定义失败.png

即原来是空格不是英文的空格。害我一直在怀疑是不是自己写的语法有问题。

其他属性判断请认真查看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:以下几个别人也遇到的异步处理的文章,处理方式和本文所讲一样。可略过

  • Delay/Wait in a test case of Xcode UI testing
  • How to use expectationForPredicate with a XCUIElementQuery UISwitch
  • Xcode UI Testing Tip:Delay/Wait

附3:UI Testing in Xcode 7这是一篇从上述Delay/Wait in a test case of Xcode UI testing的问题,别人的回答中,找到的一篇UITest的文章。写得很不错,很全,建议看。

其他参考文章

其他参考文章1、看过本文的可略过,因为那边的东西本文都提过
iOS UITests(UI自动化测试 实现)

其他参考文章2、看过本文的再看,因为那边有些东西本文没提
iOS单元测试和UI测试
iOS自动化测试的那些干货

其他参考文章3、本文未涉及的知识点
iOS 无限monkey测试解决方案
iOS客户端monkey测试
iOS自动化测试框架对比

九、WebDriverAgent的使用

在进行下节《使用Appium进行iOS的自动化测试》前,我们先了解WebDriverAgent的使用,因为《使用Appium进行iOS的自动化测试》中需要替换Appium中的WebDriverAgent;

先讲下模拟器下的使用:
1、到WebDriverAgent下载最新版本的WebDriverAgent
2、进入下载后的WebDriverAgent文件
3、执行 ./Scripts/bootstrap.sh
4、直接用Xcode打开WebDriverAgent.xcodepro文件
5、连接并选择自己的iOS设备,然后按Cmd+U,或是点击Product->Test
6、运行成功时,在Xcode控制台应该可以打印出一个Ip地址和端口号。
7、在网址上输入http://(iP地址):(端口号)/status,如果网页显示了一些json格式的数据,说明运行成功。

如果真机的话,还需要配置配置WebDriverAgentLib和WebDriverAgentRunner的证书。

  • appium官网iOS真机问题:https://github.com/appium/appium-xcuitest-driver/blob/master/docs/real-device-config.md

十、使用Appium进行自动化测试

需要
1、安装Appium-Desktop
2、安装appium-doctor
3、更新Appium中的WebDriverAgent
4、安装Appium-Python-Client

2、appium-doctor的安装

2.1、检查是否安装appium-doctor是否安装了,以及与iOS相关配置是否完整

执行appium-doctor --ios指令,查看appium-doctor的安装,以及与iOS相关配置是否完整。如下图,执行后发现未找到命令即未安装。

appium-doctor --ios.png
2.2、未安装appium-doctor时,进行安装

则我们需要执行sudo npm install appium-doctor -g来进行appium-doctor的安装

iOS 单元测试及自动化测试(只看这篇就够了)_第5张图片
sudo npm install appium-doctor -g.png

附:如果你忘了添加sudo,只是执行 npm install appium-doctor -g的话,会出现如下错误
iOS 单元测试及自动化测试(只看这篇就够了)_第6张图片
npm install appium-doctor -g.png

2.3、安装后,检查是否是否真的安装了以及与iOS相关配置是否完整

appium-doctor安装后,我们再执行appium-doctor --ios指令,查看appium-doctor是否真的安装了,以及与iOS相关配置是否完整。如果有那一项是打叉的,则进行安装就可以了。如下图发现Xcode Command Line Tools未安装。

iOS 单元测试及自动化测试(只看这篇就够了)_第7张图片
image.png

则我们Fix it选择YES,发现还是一样的问题,就自己执行xcode-select --install进行安装。
控制台执行xcode-select --install,在弹出的弹框中选择“安装”,即可进入下载和安装了,安装过程如下图:

iOS 单元测试及自动化测试(只看这篇就够了)_第8张图片
xcode-select --install安装过程中.png

安装成功后,再执行 xcode-select --install其会提示我们已经安装了。同时如果执行 sudo npm install appium-doctor -g其也会告诉我们appium-doctor与iOS的相关配置也安装成功了。
xcode-select --install安装成功后

3、更新Appium中的WebDriverAgent

进入到Appium中的WebDriverAgent目录/Applications/Appium.app/Contents/Resources/app/node_modules/appium/node_modules/appium-xcuitest-driver/,将自己下载并编译后的WebDriverAgent替换Appium原有的WebDriverAgent

4、安装python

因为我们后面是用py脚本文件执行自动化测试,所以需要安装python。

执行python --version检查python是否安装,如果未安装请执行brew install python安装

检查python是否安装.png

5、安装Appium-Python-Client

因为我们的py脚本文件中有from appium import webdriver

iOS 单元测试及自动化测试(只看这篇就够了)_第9张图片
from appium import webdriver.png

所以,我们需要安装Appium-Python-Client。如果未安装就去执行py文件,则会出现ImportError: No module named appium错误,如下图:

ImportError: No module named appium.png

所以,请确保在执行py脚本文件前,你的

5.1、下载python-client源码Appium-Python-Client是安装了的。
cd /Users/lichaoqian/Desktop
git clone [email protected]:appium/python-client.git
iOS 单元测试及自动化测试(只看这篇就够了)_第10张图片
下载python-client源码.png
5.2、安装python-client
cd python-client/
sudo python setup.py install

不要加了加sudo

iOS 单元测试及自动化测试(只看这篇就够了)_第11张图片
安装python-client命令.png

执行成功如图:


iOS 单元测试及自动化测试(只看这篇就够了)_第12张图片
安装python-client成功.png

6、执行脚本

执行python appiumSimpleDemo.py遇到的问题:

iOS 单元测试及自动化测试(只看这篇就够了)_第13张图片
Unknown device or simulator UDID.png

原因是没有安装 libimobiledevice,导致Appium无法连接到iOS的设备。
在介绍怎么安装libimobiledevice前,我们先看看若安装好libimobiledevice后,其执行的结果又是什么?截图如下:

在此之前,我还遇到的问题有ImportError: cannot import name _remove_dead_weakref,如下截图:

iOS 单元测试及自动化测试(只看这篇就够了)_第14张图片
image.png

这里我原以为只要执行``更新p就可以了,即:
iOS 单元测试及自动化测试(只看这篇就够了)_第15张图片
brew install python更新.png

如果其已经是最新版本,则其提示如下:
brew install python已经是最新.png

这时候我们去执行总不会报那个表示python版本的问题了吧。然而,实际上它的结果还是和之前一样,可是我们明明已经安装了最新的python了,为什么还是错误,到底问题出在哪里。经过一番摸索,才发现原来它执行的是python@2,而不是python,所以我就尝试着要不先去删掉python@2看看是错误吧。删除的命令如下:
brew uninstall --ignore-dependencies python@2
iOS 单元测试及自动化测试(只看这篇就够了)_第16张图片
brew uninstall --ignore-dependencies [email protected]

删除成功后,再执行一遍 python appiumSimpleDemo.py命令。这时候的结果变为如下:
iOS 单元测试及自动化测试(只看这篇就够了)_第17张图片
image.png

可以看到这时候它调用的就是python命令,而不是python@2了。
解决了python后,这时候还有另一个问题,即图上的 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呢?因为如果我们要在iOS10+的系统上使用appium,则需要安装ios-deploy。
显然我们肯定需要在iOS10+的系统上使用appium,所以我们根据它的提示 npm install -g ios-deploy去安装ios-deploy即可(不要高兴得太早)。然而它提供的命令并不能完全让我们安装成功。如下图:
iOS 单元测试及自动化测试(只看这篇就够了)_第18张图片
image.png

你肯定猜到了是sudo的问题吧,不过这里比较特殊,就是即使你加上sudo,即执行的是 sudo npm install -g ios-deploy也还是无法成功。那正确的完整的命令应该是怎么样的呢?答:这个问题的解决方法在
https://github.com/phonegap/ios-deploy/issues/188中可以找到,其实就是 sudo npm install -g ios-deploy --unsafe-perm=true。执行后,如下图所示:
iOS 单元测试及自动化测试(只看这篇就够了)_第19张图片
sudo npm install -g ios-deploy --unsafe-perm=true.png

好了,解决了这个问题后,我们再回头来执行下py脚本,看看还有什么问题没。
执行如下,
iOS 单元测试及自动化测试(只看这篇就够了)_第20张图片
成功执行py脚本.png

从图上可以看出,我们终于成功了。。。是的,你成功了。而且你看你的手机,你会发现在这个脚本的执行过程中,你的手机是在自动化测试的。

6.1、libimobiledevice的安装

执行brew install libimobiledevice --HEAD命令,进行libimobiledevice的安装。

iOS 单元测试及自动化测试(只看这篇就够了)_第21张图片
libimobiledevice的安装1.png

根据错误提示,我们执行在终端继续 sudo chown -R $(whoami) /usr/local/share/man/man3 /usr/local/share/man/man5 /usr/local/share/man/man7命令。执行成功后,回头执行之前执行没成功的 brew install libimobiledevice --HEAD命令,进行libimobiledevice的安装。可以发现这时候它就正常安装了。如下图:
iOS 单元测试及自动化测试(只看这篇就够了)_第22张图片
image.png

但执行过程中,当执行到./autogen.sh的时候又发现另外一个问题,如下图:
iOS 单元测试及自动化测试(只看这篇就够了)_第23张图片
libusbmuxd.png

这又是什么原因呢?(PS:Requested 'libusbmuxd >= 1.1.0' but version of libusbmuxd is 1.0.10这个问题,您可能在用Flutter的时候也会遇到,如果遇到解决方法跟这边一样。)

我们仔细看,会发现异常所在Requested 'libusbmuxd >= 1.1.0' but version of libusbmuxd is 1.0.10,很显然是由于系统要求的*libusbmuxd *版本和所要安装的版本不一致。那怎么解决呢?其实很简单。只要把旧的卸载了,装个新的就是了。
卸载命令为:brew uninstall --ignore-dependencies usbmuxd
安装命令为:brew install --HEAD usbmuxd
如:

iOS 单元测试及自动化测试(只看这篇就够了)_第24张图片
image.png

这时候再去执行 brew install libimobiledevice --HEAD命令,成功的截图如下:
iOS 单元测试及自动化测试(只看这篇就够了)_第25张图片
brew install libimobiledevice --HEAD安装成功.png

其他参考文章:
  • Appium自动化—浅谈iOS自动化测试环境搭建
  • https://github.com/zhshijie/appiumSimpleDemo

End

暂时结束,后续会再补充。

你可能感兴趣的:(iOS 单元测试及自动化测试(只看这篇就够了))