iOS单元测试-各种问题

编写用例时注意

1.用例只针对SDK对外开放的入口

2.注意异常情况和边界值

3.注意用例之间的依赖情况

单测用例如何按照顺序执行

测试函数的执行顺序与测试的字符大小有关系,如- (void)test001Example > - (void)test002Example > - (void)testExample

理论上单元测试不应该控制用例的执行顺序,每个case都能够单独执行,但是针对某些特殊的情况,可以适当使用一次此特性。

用例依赖的情况如何处理

当你的用例需要按照顺序执行就说明你的用例存在依赖,针对这种情况就需要进行拆分,放到不同的组(测试文件)当中来执行,比如说存在a,b,c三个用例,b和c都依赖于a,那么比较合理的做法就是将a提到另外一个文件当中进行测试,b和c放到另外一个文件中,在b和c的文件中将a放入setUp方法用作b、c执行的前提,这样就能比较好的进行测试,也减少了大量的冗余代码。

multiple calls made to -[XCTestExpectation fulfill]

出现这种情况是因为写在回调当中的XCTestExpectation被多次调用导致的,解决这个错误,网上目前有以下三种解决方案,可以根据自己的情况进行选择:

1.使用__weak修饰XCTestExpectation。

[expectation fulfill]执行一次后方法执行完毕,局部变量expectation被释放,当回调再次调起时,expectation已被释放置nil,执行fulfill不会报错。

- (void)testExample {
    NSString *username = @"shuhai";
    NSString *password = @"123";

    __weak XCTestExpectation *expectation = [self expectationWithDescription:@"登录请求返回"];

    [SHUserCenter loginWithUserName: username password: password callback:^(NSDictionary *data) {
        XCTAssertNotNil([data objectForKey:@"data"],@"返回结果不能为nil");
        [expectation fulfill];
    }];
    [self waitForExpectationsWithTimeout:self.timeout handler:nil];
}

但是该种方法存在一个问题,就是当有多个XCTestExpectation时,靠前的expectation执行fulfill时,该方法并不一定结束,导致再次调用fulfill报错,如下当noticeExpectation所在的回调被第二次调用时,expectation可能并未fulfill,变回导致noticeExpectation多次fulfill报错。

- (void)testExample {
    NSString *username = @"shuhai";
    NSString *password = @"123";

    __weak XCTestExpectation *noticeExpectation = [self expectationWithDescription:@"登录通知返回"];
    [SHUserCenter registerNotice:SHUCenterNoticeTypeLoginSuccess handle:^(NSDictionary *data) {
    XCTAssertNotNil([data objectForKey:@"data"],@"返回结果不能为nil");
    [noticeExpectation fulfill];
}];

    __weak XCTestExpectation *expectation = [self expectationWithDescription:@"登录请求返回"];
    [SHUserCenter loginWithUserName: username password: password callback:^(NSDictionary *data) {
        XCTAssertNotNil([data objectForKey:@"data"],@"返回结果不能为nil");
        [expectation fulfill];
    }];
    [self waitForExpectations:@[noticeExpectation,expectation] timeout:self.timeout];
}

2.使用__block修饰XCTestExpectation并在fulfill之后置为nil

当XCTestExpectation对象执行过fulfill之后被置为了nil,再次调用fulfill则不会报错。

- (void)testExample {
    NSString *username = @"shuhai";
    NSString *password = @"123";

    __block XCTestExpectation *expectation = [self expectationWithDescription:@"登录请求返回"];

    [SHUserCenter loginWithUserName: username password: password callback:^(NSDictionary *data) {
        XCTAssertNotNil([data objectForKey:@"data"],@"返回结果不能为nil");
        [expectation fulfill];
        expectation = nil;
    }];
    [self waitForExpectationsWithTimeout:self.timeout handler:nil];
}

这种方法在大部分情况可以可以解决问题,有种特殊的情况我们后面再讨论

3.移除回调的多次调用逻辑。

查找并移除回调多次调用的逻辑是从根本上解决这种问题的方法,但是某些业务场景下我们可能是需要回调多次调用的,针对这种情况,我们就可以采用第二种方法来解决问题。

超时之后fulfill错误无法上报

这种错误是因为等待XCTestExpectation超时测试方法执行完毕后,回调调用(比如网络不好超时的情况),而且返回了一个错误的结果,在回调当中的断言失败却无法上报错误导致的崩溃,错误信息大致如下:

Terminating app due to uncaught exception 'NSInternalInconsistencyException', 
reason: 'Unable to report test assertion failure '((data) != nil) 
failed: throwing "Unable to report test assertion failure '((data) != nil) failed' from /Users/yzq/Documents/Component/CHLUserCenter/Example/Tests/CHLUserCenterNoDataTests.m:31 because it was raised inside test case -[CHLUserCenterNoDataTests test001] which has no associated XCTestRun object. 
This may happen when test cases are constructed and invoked independently of standard XCTest infrastructure, or when the test has already finished."
' from /Users/yzq/Documents/Component/CHLUserCenter/Example/Tests/CHLUserCenterNoDataTests.m:31 because it was raised inside test case -[CHLUserCenterNoDataTests test001] which has no associated XCTestRun object. 
This may happen when test cases are constructed and invoked independently of standard XCTest infrastructure, or when the test has already finished.'

这种错误在上面提到的使用__block的情况下也不能幸免,解决方案就是使用一个__block修饰的变量,将回调当中要获取的值取出来,然后再外层进行数据判断,这样就不会导致断言在回调当中崩溃了。

- (void)testExample {
    NSString *username = @"shuhai";
    NSString *password = @"123";

    __block XCTestExpectation *expectation = [self expectationWithDescription:@"登录请求返回"];
    __block NSDictionary *returnData = nil;

    [SHUserCenter loginWithUserName: username password: password callback:^(NSDictionary *data) {
        returnData = data;
        [expectation fulfill];
        expectation = nil;
    }];
    [self waitForExpectationsWithTimeout:self.timeout handler:nil];
    XCTAssertNotNil([returnData objectForKey:@"data"],@"返回结果不能为nil");
}

Assertion failure in -[XCTestExpectation fulfill]

这个错误是当有多个XCTestExpectation时使用waitForExpectations:timeout:等待Expectation导致,导致错误的具体原因目前还不是很明确,有了解具体原因的同学可以告诉我下。

针对这个问题,我们可以使用waitForExpectationsWithTimeout:handler:来等待Expectation,这样就不会报错了。

attempt to insert nil object from objects[0]

这个错误是因为我们在前面使用__block修饰XCTestExpectation并置为nil操作的解决方案时同时使用了waitForExpectations:timeout:来等待Expectation,向数组中插入了nil。

这个问题的解决方案同上,使用waitForExpectationsWithTimeout:handler:来替换即可。所以针对大多数啊情况推荐使用waitForExpectationsWithTimeout:handler:来等待XCTestExpectation。