iOS - UnitTests 单元测试

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%。从 github 上得知:YYModel 测试覆盖度为 83%,AFNetworking 测试覆盖度为 77%,两者都是比较高的。

2、XCTest 单元测试

2.1 测试使用方法

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

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

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");
        }
  • 点击播放按钮,开始单个方法的测试

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

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");
            }];
        }

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

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

2.10 高级自动化单元测试

3、OCMock 单元测试

3.1 OCMock

  • 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 文件夹)拷贝到要测试的项目根目录下。打开工程,将拷贝的文件添加到项目工程中。

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

  • 3、打开 Build Settings,搜索 Other Linker Flags,设置如下
        -force_load
        "$(SRCROOT)/OCMock/libOCMock.a"
        -ObjC

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

    • "$(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 调用方法,并使用断言判断。
  • 如果在测试时,我们只想在控制台中看见这个方法的输出信息,可以点击方法前面的一个小播放按钮

  • 控制台输出

  • 下面来编写一个稍微复杂点的 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 使用

5、UITests UI 测试

5.1 UITests

  • 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 中的部分属性

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

5.3 添加 UITests

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

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

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

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

5.4 创建测试代码

  • 1、手动创建测试代码

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

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

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

    • 这时候可以分析测试代码的语法,以便你自己手动修改或者手写测试代码。
  • 3、开始测试
    • 点击 testExample 方法旁边的播放按钮,它就开始进行自动测试了,这时候你会看到 App 在自动操作。

时间: 2024-09-28 17:56:34

iOS - UnitTests 单元测试的相关文章

iOS基础—单元测试

单元测试(unit testing):对软件中最小可测试单元进行检查和验证.一般面向过程的语言中,基本单元为函数,面向对象的语言中,基本单元通常是类,其实对于一个手机上的app来说基本单元也可以是一个UI页面.平时我们写了一个函数,执行以下看是否正常工作,也属于单元测试. 测试用例(test case):对测试任务的描述,体现测试方案.方法.技术和策略.内容包括测试目标.测试环境.输入数据.输出数据.测试步骤.预期结果.测试脚本等. 它是一种检验行为,便于我们写出高质量代码. 它是一种设计行为,

浅谈iOS单元测试

什么是单元测试? 单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证.对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等.总的来说,单元就是人为规定的最小的被测功能模块. iOS中单元测试有什么意义? 大型项目开发,功能比较繁琐,代码量比较大,调试某一块功能需要不断的Command+R运行调试,很显然这样的功能是非常非常的低效的,编写过程中以及App功能完成后

《iOS开发指南》第二版 iOS7版-源码-样章-目录,感谢大家一直以来的支持

<iOS开发指南-从0基础到AppStore上线>第二版 iOS7版正式出版了 感谢大家一直以来的支持! 改版后采用全新的ios 7 api,详细介绍了最新的ios 7 开发相关的知识点,全部案例以iOS7版本SDK重新编译. 新增:iOS 7中文字排版和渲染引擎--Text Kit:      iOS6升级到iOS7遇到的问题与解决方法:      着重讲解iOS分层架构设计: 更新无处不在,更多新增内容请详细阅读本书 京东销售地址:http://item.jd.com/11419483.h

IOS6.1单元测试持续集成实践

最近项目测试需要,调研并实践了下IOS下单元测试工具和框架.目前比较流行的工具有xcode自带的OCUnit.GHUnit等,我选择的是GHUnit,因为相比OCUnit,GHUnit具有如下优势: 1.开源框架 2.支持重复测试.单一测试.集成测试. 3.断言方法丰富 4.支持持续集成 5.测试类型多样(UI和Command Line) 官方地址如下:http://gabriel.github.io/gh-unit/ GitHub下载地址:https://github.com/gabriel/

iOS单元测试1

iOS单元测试1 iOS单元测试分为两种类型的测试: 应用测试.应用程序测试可以检查app的代码组件,比如计算机的算术运算的例子.你可以利用应用程序测试来确保你的UI空间控件保持原有位置,并且你的控件和控制器对象能够和对象模型正确地工作. 逻辑测试(库测试).逻辑测试可以检查独立代码的行为是否正确.利用逻辑测试,你可以将整个库的组件放在一起进行测试,通常测试对象是对象和方法. 性能测试:所谓性能测试,主要是评估一段代码运行的时间.(自己添加的,个人觉得应该也属于一类测试把).性能测试的格式:-

iOS开发中的单元测试(三)URLManager中的测试用例解析

URLManager是一个基于UINavigationController和UIViewController,以URL Scheme为设计基础的导航控件,目的是实现ViewController的松耦合,不依赖. 准备框架,定义基类 首先按照之前的两篇文章介绍的方法导入单元测试框架和匹配引擎框架,建立好测试Target,并配置编译选项. 定义测试用例基类:UMTestCase(代码1),其他用例全部继承自UMTestCase. #import <GHUnitIOS/GHTestCase.h> @

iOS开发中的单元测试(二) 让断言活泼起来的匹配引擎

上一篇文章简单介绍了OCUnit和GHUnit两款iOS开发中较为常见的单元测试框架,本文进一步介绍单元测试 中的另一利器--匹配引擎(Matcher Engine).匹配引擎可以替代断言方法,配合单元测试引擎使用,测试 用例可以更多样化,更细致. 传统断言提供的方法数量和功能都有限,以导读中提到的两款框架为例 ,即使是断言相对丰富的GHUnit也只是提供了38种断言方法,范围仅涵盖了逻辑比较,异常和出错等少数几方 面,仍然很单一.而使用匹配引擎代替断言,可能性就大大丰富了,除了普通断言支持的规

iOS开发中的单元测试(一) 对比OCUnit和GHUnit

本文不讨论单元测试是什么,或者它之于一个工程的利弊,我认为单元测试是一个开发者保证产出代码质 量的有效工具.本文从使用者的角度对比当下比较流行的两款单元测试框架,给大家提供一些选用建议.如果 你还不甚了解单元测试在工程中所起到的作用,或者还不知道TDD的开发模式,可参考:Test-Driven Development和Unit Testing. 本文对比两个iOS开发中常见的单元测试框架:OCUnit,被官方集成进XCode 4.x版本中:GHUnit,被推荐 最多的测试框架,带GUI界面.初窥

iOS中如何对具有复杂依赖的SDK在真机上进行单元测试

单元测试在软件开发中一直有着极其重要的地位,iOS的开发也不例外.随着App规模的不断膨胀,开发也逐渐的趋向模块化,开发者常常以库的形式封装功能,最后组成App.此时由于App结构变得复杂,各种库又可能存在着相互依赖的缘故,单元测试也随之变得复杂起来.开发者可能面临着一系列问题,比如:单元测试如何处理这些依赖?如何在真机上运行测试?如何在App所在的环境中运行测试?本文将用一个模拟的开发环境逐一进行讨论. 目录 问题 搭建SDK开发环境 第三方库:EC3rdFramework 开发中的SDK:E