iOS-UnitTests单元测试

本文简介

  • 1.UnitTests
  • 2.XCTest 单元测试
  • 3.OCMock 单元测试
  • 4.UITests UI 测试

1.UnitTests

  • 在计算机编程中,单元测试(又称为模块测试,Unit Testing)是针对程序模块(软件设计的最小单位)来进行正确性校验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。
  • 通常来说,程序员每修改一次代码就会修改某个单元,那我们就可以对这个单元做修改的验证(单元测试),在编写程序的过程中前后很可能要进行多次单元测试,已证实程序达到软件规格书(产品需求)要求的工作目标,而且没有程序错误。虽然单元测试不是什么必须的,但也不坏,这牵扯到项目管理的政策决定。
  • 单元测试可以方便测试一些功能是否正常运行,调试接口是否正确使用。有时候你可能是为了测试某一个网络接口,然后每次都重新启动并且经过很多操作之后测试到了那个网络接口,如果使用了单元测试,就可以直接测试那个方法,相对方便很多,比如由于修改较多,我们想测试一下分享功能是否正常,这时候就有用了,而不是重新启动程序,进入到分享界面,点击分享,填写分享内容。其实单元测试并没有降低我们打代码的效率,我们可以在单元测试通过了,直接用到相应的地方。
  • 当然单元测试也有一些高级的作用,比如自动发布、自动测试(特别在一些大的项目,以防止程序被误改或引起新的问题)。

1.1 iOS中的单元测试框架

  • XCTest 是苹果自带的测试框架。
  • GHUnit 是一个可视化的测试框架,有了它,你可以点击APP来决定测试哪个方法,并且可以点击查看测试结果等。
  • OCMock是模拟某个方法或者属性的返回值,你可能会疑惑为什么要这样做?使用模型生成的模型对象,再传进去不就可以了?答案是可以的,但是有特殊的情况,比如你测试的方法A,方法A里面调用到了方法B,而且方法B是有参数传入,但又不是方法A所提供,这时候,你可以使用OCMock来模拟方法B返回的值。在不影响测试的情况下,就可以这样去模拟。除了这些,在没有网络的情况下,也可以通过OCMock模拟返回的数据。
  • UITests 是通过代码化来实现自动点击界面,输入文字等功能。靠人工操作的方式来覆盖所有测试用例是非常困难的,尤其是加入新功能以后,旧的功能也要重新测试一遍,这导致了测试需要花非常多的时间来进行回归测试,这里产生了大量重复的工作,而这些重复的工作有些是可以自动完成的,这时候UITests就可以帮助解决这个问题了。

1.2单元测试思路

  • 1,单元测试是以代码测试代码。不是靠NSLog来测试,NSLog是程序员用眼睛看的笨方法,而是使用断言来测试的,提前预判条件必须满足。
    XCTAssert(expression, ...)
    XCTAssert(条件, 不满足条件的描述)
  • 2.单元测试与应用程序开发属于共存关系,而非嵌入关系,所以必须创建一个单独的测试目标。
  • 3.可以在单元测试类中编写单独的测试用例方法,这些方法与普通的方法类似,但是方法名称必须以test开头,且不能有参数,不然不会识别为测试方法。
  • 4.测试方法可以直接写在- (void) testExample中,或者写在以test开头的测试用例方法中。
  • 5.单元测试需要在真机上运行,为了能够在设备中真实地运行应用程序用例,需要安装开发配置文件(development provision file)。
  • 6.需要注意,在应用程序上运行单元测试用例并不是一个交互过程,所有的运行控制(包括提供值)都由测试用例自身掌握。
  • 7.不是所有的方法都需要测试,例如私有方法不需要测试,只有暴露在.h中的方法需要测试。
  • 8.一般而言,代码的覆盖度大概在50%-70%。

2、XCTest单元测试

2.1 测试使用方法

  • 单元调试操作,两种方法,按快捷键Command + U 进行单元测试,这个快捷键是全部测试。


    image.png
  • 调试可以在断点处调试,也可以在函数部分调试,错误提示是在断点处显示,不会在平台展示。


    image.png

2.2测试类中的方法

  • 测试方法
- (void)setUp {
        [super setUp];
        // Put setup code here. This method is called before the invocation of each test method in the class.

        // 初始化的代码,在测试方法调用之前调用
    }

    - (void)tearDown {
        // Put teardown code here. This method is called after the invocation of each test method in the class.

        // 释放测试用例的资源代码,这个方法会每个测试用例执行后调用

        [super tearDown];
    }

    - (void)testExample {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.

        // 测试用例的方法
    }

    - (void)testPerformanceExample {
        // This is an example of a performance test case.

        // 测试性能的方法,有 Instrument 调试工具之后,其实这个没毛用

        [self measureBlock:^{
            // Put the code you want to measure the time of here.

            // 需要测试性能的代码
        }];
    }

2.3 测试函数

  • 测试函数
// 生成一个失败的测试
    XCTFail(format…);

    // 为空判断

        // 为空判断,a1 为空时通过,反之不通过
        XCTAssertNil(a1, format...);

        // 不为空判断,a1 不为空时通过,反之不通过
        XCTAssertNotNil(a1, format…);

    // 为真判断

        // 为真判断,当 expression 求值为 True 时通过
        XCTAssert(expression, format...);

        // 为真判断,当 expression 求值为 True 时通过
        XCTAssertTrue(expression, format...);

        // 为假判断,当 expression 求值为 False 时通过
        XCTAssertFalse(expression, format...);

    // 相等判断

        // 相等判断,[a1 isEqual:a2] 值为 True 时通过,其中一个不为空时,不通过
        XCTAssertEqualObjects(a1, a2, format...);

        // 不等判断,[a1 isEqual:a2] 值为 False 时通过
        XCTAssertNotEqualObjects(a1, a2, format...);

        // 相等判断,当 a1 和 a2 是 C 语言标量、结构体或联合体时使用,a1 == a2 值为 True 时通过
        XCTAssertEqual(a1, a2, format...);

        // 不等判断,当 a1 和 a2 是 C 语言标量、结构体或联合体时使用
        XCTAssertNotEqual(a1, a2, format...);

        // 相等判断,double 或 float 类型,提供一个误差范围,当在误差范围(+/-accuracy)以内相等时通过测试
        XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...);

        // 不等判断,double 或 float类型,提供一个误差范围,当在误差范围以内不等时通过测试
        XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...);
// 异常判断

        // 异常判断,当 expression 发生异常时通过,反之不通过
        XCTAssertThrows(expression, format...);

        // 异常判断,当 expression 发生 specificException 异常时通过,反之发生其他异常或不发生异常均不通过
        XCTAssertThrowsSpecific(expression, specificException, format...);

        // 异常判断,当 expression 发生具体异常、具体异常名称的异常时通过测试,反之不通过
        XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...);

        // 异常判断,当 expression 没有发生异常时通过测试
        XCTAssertNoThrow(expression, format…);

        // 异常判断,当 expression 没有发生具体异常、具体异常名称的异常时通过测试,反之不通过
        XCTAssertNoThrowSpecific(expression, specificException, format...);

        // 异常判断,当 expression 没有发生具体异常、具体异常名称的异常时通过测试,反之不通过
        XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...);

2.4测试的基本使用

  • 基本使用
 - (void)testExample {

        NSLog(@"自定义测试 testExample");
        int a = 3;
        XCTAssertTrue(a == 0, "a 不能等于 0");
    }
  • 点击播放按钮,开始每个方法的测试


    image.png
  • 出现如下结果,由于我们断言a是等于0的,而a等于3,所以测试没通过。


    image.png

2.5测试问题解决

  • 问题描述:fatal error: 'XCTest/XCTest.h' file not found
  • 解决办法
在报错的 Target 中的 Building settings 中 FRAMEWORK_SEARCH_PATHS* 添加

    $(PLATFORM_DIR)/Developer/Library/Frameworks

2.6单例测试

  • 单例要在并发条件下调试
// 测试是否为单例
    - (void)testAudioManagerSingle {

        // 要在并发条件下测试

        NSMutableArray *managers = [NSMutableArray array];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            QAudioManager *tempManager = [[QAudioManager alloc] init];
            [managers addObject:tempManager];
        });

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            QAudioManager *tempManager = [[QAudioManager alloc] init];
            [managers addObject:tempManager];
        });

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            QAudioManager *tempManager = [QAudioManager defaultManager];
            [managers addObject:tempManager];
        });

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            QAudioManager *tempManager = [QAudioManager defaultManager];
            [managers addObject:tempManager];
        });

        QAudioManager *managerOne = [QAudioManager defaultManager];

        // 这里是判断数组中的对象是否一致
        [managers enumerateObjectsUsingBlock:^(QAudioManager *obj, NSUInteger idx, BOOL * _Nonnull stop) {

            XCTAssertEqualObjects(managerOne, obj, @"QAudioManager is single");

            XCTAssertNotEqualObjects(managerOne, obj, @"QAudioManager is not single");
        }];
    }
image.png

2.7性能测试

  • 性能测试
    测试一段代码(函数、方法)的执行时间,我们通常是用到 CFAbsoluteTimeGetCurrent() 或者 CACurrentMediaTime() 函数,通过差值来计算出时间间隔。
+ (instancetype)personWithDict:(NSDictionary *)dic {

        NSString *str1;
        for (NSString *str in dic) {
            str1 = [str stringByAppendingString:str];
        }
        str1 = nil;
        Person *one = [[self alloc] init];
        return one;
    }

    - (void)testPerformanceExample {
        // This is an example of a performance test case.

        // 测试性能的方法,有 Instrument 调试工具之后,其实这个没毛用

        [self measureBlock:^{
            // Put the code you want to measure the time of here.

            // 需要测试性能的代码

            NSTimeInterval start = CACurrentMediaTime();

            // 测试用例,循环10000次,为了演示效果
            for (NSInteger i = 0; i < 10000; i++) {
                [Person personWithDict:@{@"name":@"zhang", @"age":@20}];
            }

            // 传统测试代码耗时方法
            NSLog(@"%lf, 我是香蕉大大", CACurrentMediaTime() - start);

        }];
    }
image.png

2.8逻辑测试

 // 逻辑测试
    - (void)testNewPerson {

        // 1.测试 name 和 age 是否一致
        [self checkPersonWithDict:@{@"name":@"zhou", @"age":@30}];

        /** 2.测试出 age 不符合实际,那么需要在字典转模型方法中对 age 加以判断:
         if (obj.age <= 0 || obj.age >= 130) {
            obj.age = 0;
         }
         */
        [self checkPersonWithDict:@{@"name":@"zhang", @"age":@200}];

        // 3.测试出 name 为 nil 的情况,因此在 XCTAssert 里添加条件:“person.name == nil“
        [self checkPersonWithDict:@{}];

        // 4.测试出 Person 类中没有 title 这个 key,在字典转模型方法中实现:- (void)setValue:(id)value forUndefinedKey:(NSString *)key {}
        [self checkPersonWithDict:@{@"name":@"zhou", @"age":@30, @"title":@"boss"}];

        // 5.总体再验证一遍,结果 Build Succeeded,测试全部通过
        [self checkPersonWithDict:@{@"name":@"zhou", @"age":@-1, @"title":@"boss"}];
    }

    // 根据字典检查新建的 person 信息
    - (void)checkPersonWithDict:(NSDictionary *)dict {

        Person *person = [Person personWithDict:dict];

        NSLog(@"%@",person);

        // 获取字典中的信息
        NSString *name = dict[@"name"];
        NSInteger age = [dict[@"age"] integerValue];

        // 1.检查名字
        XCTAssert([name isEqualToString:person.name] || person.name == nil, @"姓名不一致");
 // 2.检查年龄
        if (person.age.integerValue > 0 && person.age.integerValue < 130) {
            XCTAssert(age == person.age.integerValue, @"年龄不一致");
        } else {
            XCTAssert(person.age == 0, @"年龄超限");
        }
    }

2.9网络请求测试

  • 安装 AFNetworking 和 STAlertView

  • 由于测试方法主线程执行完就会结束,所以需要设置一下,否则没法查看异步返回结果。在方法结束前设置等待,调回回来的时候再让它继续执行。

 //waitForExpectationsWithTimeout是等待时间,超过了就不再等待往下执行。
    #define WAIT do {\
    [self expectationForNotification:@"RSBaseTest" object:nil handler:nil];\
        [self waitForExpectationsWithTimeout:30 handler:nil];\
    } while (0);

    #define NOTIFY \
    [[NSNotificationCenter defaultCenter]postNotificationName:@"RSBaseTest" object:nil];
  • 增加测试方法 testRequest
- (void)testRequest{
        // 获得请求管理者
        AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
        mgr.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",nil];

        // 发送 GET 请求
        [mgr GET:@"http://www.weather.com.cn/adat/sk/101110101.html" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
            XCTAssertNotNil(responseObject, @"返回出错");
            NOTIFY // 继续执行
        } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
            XCTAssertNil(error, @"请求出错");
            NOTIFY // 继续执行
        }];
        WAIT  //暂停
    }
  • 有时候我们想测试一下整个流程是否可以跑通,比如获取验证码、登录、上传头像,查询个人资料。其实只要输入验证码就可以完成整个测试。这时候就需要用到输入框了,以便程序继续执行。使用了一个第三方的弹出输入框 STAlertView,前面已经设置。
self.stAlertView = [[STAlertView alloc] initWithTitle:@"验证码" 
                                                  message:nil 
                                            textFieldHint:@"请输入手机验证码" 
                                           textFieldValue:nil 
                                        cancelButtonTitle:@"取消" 
                                         otherButtonTitle:@"确定" 
                                        cancelButtonBlock:^{
        // 点击取消返回后执行
        [self testAlertViewCancel];
        NOTIFY  // 继续执行
    } otherButtonBlock:^(NSString *b) {
        // 点击确定后执行
        [self alertViewComfirm:b];
         NOTIFY     // 继续执行
    }];

    [self.stAlertView show];

3.OCMock单元测试

3.1OCMock

  • Mock 测试

Mock 测试是个很神奇而又很酷的技术,在测试过程中,对于一些不容易构造或不容易获取的对象,此时你可以创建一个虚拟的对象(mock object)来完成测试。

例如你可能要尝试 100 次才会返回一个 NSError,通过 mock object 你可以自行创建一个 NSError 对象,测试在出错情况下程序的处理是否符合你的预期。

例如你要连接服务器但是服务器在实验室,你在外工作的时候就无法测试了,这个时候你可以创建一个虚拟的服务器,并返回一些你指定的数据,从而绕过服务器。

例如假设你要访问一个数据库,但是访问过程的开销巨大,这时你可以虚拟一个数据库,并且返回一些自行定制的数据,从而绕过了数据库的访问。

Mock 的思想很简单:没有条件?我们就自行创造条件。

  • OCMock
  • OCMock 是一个用于为 iOS 或 macOS 项目配置 Mock 测试的开源项目,如果目标是 iOS 项目那么生成的是静态库,如果是 macOS 项目生成的是框架。OCMock 其实现思想就是根据要 mock 的对象的 class 来创建一个对应的对象,并且设置好该对象的属性和调用预定方法后的动作(例如返回一个值,调用代码块,发送消息等等),然后将其记录到一个数组中,接下来开发者主动调用该方法,最后做一个 verify(验证),从而判断该方法是否被调用,或者调用过程中是否抛出异常等。

  • OCMock 官网

  • iOS Project Setup:在 iOS 项目中配置 OCMock 的教程

  • erikdoe / ocmock:在 GitHub 上的示例项目,可以参考下其中的一些配置参数

  • OCMock Download:OCMock 的静态库、框架和工程文件(可以在这里看 OCMock 的源码实现)下载地址,已经打包成 dmg 格式了。

3.2配置OCMock

  • 1、下载 OCMock Download 的 dmg 文件,将 iOS library 文件夹中的文件(libOCMock.a 和 OCMock 文件夹)拷贝到要测试的项目根目录下。打开工程,将拷贝的文件添加到项目工程中。


    image.png
  • 2、打开 OCMockDemoTests Target 的 Build Phases,添加 libOCMock.a 到要链接的类库中。


    image.png
  • 3、打开 Build Settings,搜索 Other Linker Flags,设置如下
-force_load
    "$(SRCROOT)/OCMock/libOCMock.a"
    -ObjC
image.png
  • 这里的 -ObjC 表示告诉链接器,要把 OC 类和 Category 加载到工程中,但是该设置有 Bug,所以还要用 -all_load 或者 -force_load 来加载静态库中没有加载进来的 Category。如果使用 -all_load 会把所有相关无关的文件都 load 进来,使得目标程序变得更大,所以用 -force_load 来指定要加载的静态库就可以了,下面的 "$(SRCROOT)/OCMock/libOCMock.a" 就是静态库文件在 Finder 中的路径。
  • 4、在搜索Header Search Paths,设置如下


    image.png
  • "$(SRCROOT)/OCMock" 给出的是 OCMock 的头文件在 Finder 中的路径,因此该选项告诉编译器应该到哪里去寻找 OCMock 静态库的头文件。

3.3 编写mock测试

  • 新建一个 test case class 类,基类为 XCTestCase,命名为 MockTableTests。
  • 首先我们测试一下 TableDataSource 的 numberOfRowsInSection 方法是否返回了正确的值,测试代码如下
- (void)testNumberOfRows {

        // 创建 Table View 的 DataSource
        TableViewCellConfigureBlock cellConfigureBlock = ^(UITableViewCell *cell, NSString *item) {
            cell.textLabel.text = item;
        };

        TableDataSource *tableSource = [[TableDataSource alloc] initWithItems:@[@"1", @"2", @"3"]
                                                               CellIdentifier:@"foo"
                                                           ConfigureCellBlock:cellConfigureBlock];

        // 创建 mock table view
        id mockTableView = [OCMockObject mockForClass:[UITableView class]];

        // 断言
        XCTAssertEqual([tableSource tableView:mockTableView numberOfRowsInSection:0], (NSInteger)3, 
                       @"Mock table returns a bad number of rows in section 0");
    }
  • 1、首先创建 data source,用于下文中调用 numberOfRowsInSection 方法。注意这里 Table View 中的内容 @[@"1", @"2", @"3"] 是需要我们手动配置的。这里也体现了 mock 的一个局限性,就是 mock object 的关键属性都要我们自己定制,如果要模拟的对象非常的大,那么创建一个 mock object 的成本将远远大于单元测试带来的效益。
  • 2、如果要单独测试 numberOfRowsInSection 方法,我们就需要有一个 TableView,因此要通过 OCMockObject 的 mockForClass 类方法来创建一个 mock table view。
  • 3、通过 data source 调用方法,并使用断言判断。
    如果在测试时,我们只想在控制台中看见这个方法的输出信息,可以点击方法前面的一个小播放按钮


    image.png

    控制台输出


    image.png

    下面来编写一个稍微复杂点的 mock 测试,用来测试 UITableViewDataSource 中的 cellForRowAtIndexPath 方法。
- (void)testCellConfiguration {

        // 创建 Table data source
        __block UITableViewCell *configuredCell = nil;
        __block id configuredObject = nil;
        TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b) {
            configuredCell   = a;
            configuredObject = b;
        };
        TableDataSource *dataSource = [[TableDataSource alloc] initWithItems:@[@"a", @"b"]
                                                              CellIdentifier:@"foo"
                                                          ConfigureCellBlock:block];

        // 创建 mock table view
        id mockTableView = [OCMockObject mockForClass:[UITableView class]];

        // 设定 mock table view 的行为
        UITableViewCell *cell = [[UITableViewCell alloc] init];
        [[[mockTableView expect] andReturn:cell] dequeueReusableCellWithIdentifier:@"foo"
                                                                      forIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
        // [[[mockTableView stub] andReturn:cell] dequeueReusableCellWithIdentifier:@"foo" forIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

        // 主动调用 cellForRowAtIndexPath 方法
        id result = [dataSource tableView:mockTableView
                    cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];

        // 验证 mock table view 的行为
        [mockTableView verify];

        // 断言
        XCTAssertEqual(result, cell, @"Should return the dummy cell.");
        XCTAssertEqual(configuredCell, cell, @"This should have been passed to the block.");
        XCTAssertEqualObjects(configuredObject, @"a", @"This should have been passed to the block.");
    }
  • 1、创建 Table data source,用于下文调用 cellForRowAtIndexPath 方法。

  • 2、创建 mock table view。

  • 3、如果 mock table view 调用了 dequeueReusableCellWithIdentifier:@"foo" forIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]] 方法,那么就返回上面已经创建好的 UITableViewCell 对象,expect 方法表示该方法必须被调用(见5.)。

  • 4、通过 Table data source 主动调用 cellForRowAtIndexPath 方法,此时会触发 mock table view 调用 dequeueReusableCellWithIdentifier:forIndexPath: 方法。

  • 5、最后要调用 verify 方法,用于验证 mock table view 的行为。如果 mock table view 在某个方法中调用了 expect,那么该方法必须在 verify 之前被调用,否则测试无法通过。如果 mock table view 调用的是 stub,那么 verify 时 OCMock 并不关心该方法是否调用过,只会关心调用过程是否发生异常或有测试被拒绝等。

  • 6、断言,在这里进行各种比较。

4、GHUnit单元测试

4.1 GitHub

  • 可能大家都注意到了,在运行测试后,控制台中的输出可以用惨不忍睹来形容。这时我们可以尝试另一个工具:GHUnit 框架,这个工具是有 GUI 的。

  • gh-unit / gh-unit:该项目在 GitHub 上的地址。

  • guide_testing Document:编写测试的参考文档。

4.2 GHUnit使用

  • 具体详解见 GHUnit

5、UITests UI测试

5.1UITests

  • UITests是一个自动测试UI与交互的Testing组件。它可以通过编写代码、或者是记录开发者的操作过程并代码化,来实现自动点击某个按钮、视图,或者自动输入文字等功能。
  • 在实际的开发过程中,随着项目越做越大,功能越来越多,仅仅靠人工操作的方式来覆盖所有测试用例是非常困难的,尤其是加入新功能之后,旧的功能也要重新测试一遍,这导致了测试需要花非常多的时间来进行回归测试,这里产生了大量重复的工作,而这些重复的工作有些是可以自动完成的,这时候UITests就可以帮助解决这个问题了。

5.2测试元素语法

  • 1、XCUIApplication

继承 XCUIElement,这个类掌管应用程序的生命周期,里面包含两个主要方法

launch():启动程序

terminate():终止程序

  • 2、XCUIElement

继承 NSObject,实现协议 XCUIElementAttributes, XCUIElementTypeQueryProvider

可以表示系统的各种UI元素

  • 3、exist

可以让你判断当前的 UI 元素是否存在,如果对一个不存在的元素进行操作,会导致测试组件抛出异常并中断测试

  • 4、descendantsMatchingType(type:XCUIElementType)->XCUIElementQuery:

取某种类型的元素以及它的子类集合

  • 5、childrenMatchingType(type:XCUIElementType)->XCUIElementQuery:

取某种类型的元素集合,不包含它的子类

这两个方法的区别在于,你仅使用系统的 UIButton 时,用 childrenMatchingType 就可以了,如果你还希望查询自己定义的子 Button,就要用 descendantsMatchingType

  • 6、另外 UI 元素还有一些交互方法

    • tap():点击
    • doubleTap():双击
    • pressForDuration(duration: NSTimeInterval):长按一段时间,在你需要进行延时操作时,这个就派上用场了
    • swipeUp():这个响应不了 pan 手势,暂时没发现能用在什么地方,也可能是 beta 版的 bug,先不解释
    • typeText(text: String):用于 textField 和 textView 输入文本时使用,使用前要确保文本框获得输入焦点,可以使用 tap() 函数使其获得焦点
  • 7、XCUIElementAttributes 协议

    • 里面包含了 UIAccessibility 中的部分属性


      image.png
  • 可以方便你查看当前元素的特征,其中 identifier 属性可用于直接读取元素,不过该属性在 UITextField 中有 bug,暂时不清楚原因
  • 8、XCUIElementTypeQueryProvider 协议
  • 里面包含了系统中大部分 UI 控件的类型,可通过读属性的方式取得某种类型的 UI 集合,部分属性截图如下


    image.png

5.3 添加UITests

  • 1、如果是新项目,则创建工程的时候可以直接勾选选项,如下图


    image.png
  • 2、如果是已有的项目,可以通过添加 target 的方式添加一个 UI Tests,点击 xcode 的菜单,找到 target 栏


    image.png
  • 在 Test 选项中选择 Cocoa Touch UI Testing Bundle


    image.png
  • 3、这时候 test 组件添加成功,它在项目中的位置如下图所示.


5.4创建测试代码

  • 1、手动创建测试代码
  • 打开测试文件,在testExample()方法中添加测试代码,如果不知道如何写测试代码,则可以参考自动生成的代码样式


    image.png
  • 2、自动生成测试步骤
  • 选择测试文件后,点击录制按钮


    image.png

*这时候开始进行操作,他会记录你的操作步骤,并生成测试代码,下图就是在一些操作后自动生成的测试代码。


image.png
  • 3、开始测试
  • 点击testExample方法旁边的播放按钮,他就开始进行自动测试了,这时候你会看到APP在自动操作


    image.png

原文地址:https://www.cnblogs.com/QianChia/p/6379191.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,201评论 0 17
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,036评论 1 32
  • Mock 方法是单元测试中常见的一种技术,它的主要作用是模拟一些在应用中不容易构造或者比较复杂的对象,从而把测试与...
    熊熊要更努力阅读 28,190评论 2 25
  • 《人生》是我最喜欢的作家路遥的中篇小说,也是他的成名作。这部作品,非常容易引起读者的思考,它会让你在不同的阶段,不...
    历史妞阅读 1,021评论 2 7
  • 虽然图片显示的有点时令不对,但这真的是我自己很喜欢的款式之一,相信夏天配个白T恤、白裙子同样美美哒~
    纤洛阅读 288评论 0 1