XCTestExpectation:如何避免在等待上下文结束后调用fulfill方法?

时间:2014-12-18 20:53:41

标签: xctest

我正在使用Xcode 6的新异步测试功能。当异步任务在超时之前结束时,一切正常。但是如果任务花费的时间比超时时间长,事情会变得更复杂。

以下是我正在进行的测试:

@interface AsyncTestCase : XCTestCase @end

@implementation AsyncTestCase

// The asynchronous task would obviously be more complex in a real world scenario.
- (void) startAsynchronousTaskWithDuration:(NSTimeInterval)duration completionHandler:(void (^)(id result, NSError *error))completionHandler
{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(duration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        completionHandler([NSObject new], nil);
    });
}

- (void) test1TaskLongerThanTimeout
{
    XCTestExpectation *expectation = [self expectationWithDescription:@"Test 1: task longer than timeout"];
    [self startAsynchronousTaskWithDuration:4 completionHandler:^(id result, NSError *error) {
        XCTAssertNotNil(result);
        XCTAssertNil(error);
        [expectation fulfill];
    }];
    [self waitForExpectationsWithTimeout:2 handler:nil];
}

- (void) test2TaskShorterThanTimeout
{
    XCTestExpectation *expectation = [self expectationWithDescription:@"Test 2: task shorter than timeout"];
    [self startAsynchronousTaskWithDuration:5 completionHandler:^(id result, NSError *error) {
        XCTAssertNotNil(result);
        XCTAssertNil(error);
        [expectation fulfill];
    }];
    [self waitForExpectationsWithTimeout:10 handler:nil];
}

@end

不幸的是,在超时过期后调用fulfill方法会导致测试套件出现此错误:

  

API违规 - 在等待上下文结束后调用 - [XCTestExpectation fulfill]。

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'API violation - called -[XCTestExpectation fulfill] after the wait context has ended.'
*** First throw call stack:
(
  0   CoreFoundation   0x000000010c3a6f35 __exceptionPreprocess + 165
  1   libobjc.A.dylib  0x000000010a760bb7 objc_exception_throw + 45
  2   CoreFoundation   0x000000010c3a6d9a +[NSException raise:format:arguments:] + 106
  3   Foundation       0x000000010a37d5df -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 195
  4   XCTest           0x0000000115c48ee1 -[XCTestExpectation fulfill] + 264
  ...
)
libc++abi.dylib: terminating with uncaught exception of type NSException

当然,我可以在调用fulfill方法之前检查测试是否已完成:

- (void) test1TaskLongerThanTimeout
{
    XCTestExpectation *expectation = [self expectationWithDescription:@"Test 1: task longer than timeout"];

    __block BOOL testIsFinished = NO;
    [self startAsynchronousTaskWithDuration:4 completionHandler:^(id result, NSError *error) {
        if (testIsFinished) {
            return;
        }
        XCTAssertNotNil(result);
        XCTAssertNil(error);
        [expectation fulfill];
    }];

    [self waitForExpectationsWithTimeout:2 handler:^(NSError *error) {
        testIsFinished = YES;
    }];
}

但这似乎过于复杂,使得测试更难以阅读。我错过了什么吗?有没有更简单的方法来解决这个问题?

3 个答案:

答案 0 :(得分:43)

是的,有一种更简单的方法可以避免此API违规问题:只需将期望变量声明为__weak即可。虽然没有明确记录,但超时到期时会发布预期。因此,如果任务花费的时间超过超时,则在调用任务完成处理程序时,期望变量将为nil。因此fulfill方法将在nil上调用,什么都不做。

- (void) test1TaskLongerThanTimeout
{
    __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Test 1: task longer than timeout"];
    [self startAsynchronousTaskWithDuration:4 completionHandler:^(id result, NSError *error) {
        XCTAssertNotNil(result);
        XCTAssertNil(error);
        [expectation fulfill];
    }];
    [self waitForExpectationsWithTimeout:2 handler:nil];
}

答案 1 :(得分:12)

我遇到了同样的问题但在我的情况下我需要上面答案的Swift版本。

我正在为OSX开发OpenStack Swift Drive。当使用Finder在本地删除文件夹时,删除最终会传播到服务器,我需要一个等待服务器更新的测试。

为了避免API违规崩溃,我已经将我的期望改为“弱变量”,并将调用更改为“zeroFoldersExpectation?.fulfill()”并使用额外的'?'因为期望现在是可选的并且可能变为零,在这种情况下,履行调用被忽略。这解决了崩溃问题。

func testDeleteFolder()
{
    Finder.deleteFolder()

    weak var zeroFoldersExpectation=expectationWithDescription("server has zero folders")
    Server.waitUntilNServerFolders(0, withPrefix: "JC/TestSwiftDrive/", completionHandler: {zeroFoldersExpectation?.fulfill()})
    waitForExpectationsWithTimeout(10, handler: {error in})

}

答案 2 :(得分:6)

而不是将expectation创建为weak变量(如this answer中所述),我认为您还可以设置为block变量,并且在{的完成处理程序中为零{1}}:

waitForExpectationsWithTimeout

通过这种方式,您确信ARC不会过快地释放- (void) test1TaskLongerThanTimeout { __block XCTestExpectation *expectation = [self expectationWithDescription:@"Test 1: task longer than timeout"]; [self startAsynchronousTaskWithDuration:4 completionHandler:^(id result, NSError *error) { XCTAssertNotNil(result); XCTAssertNil(error); [expectation fulfill]; }]; [self waitForExpectationsWithTimeout:2 handler:^(NSError *error) { expectation = nil; }]; }