【问题标题】:Is it safe to call XCTestExpectation fulfill method on background thread?在后台线程上调用 XCTestExpectation 实现方法是否安全?
【发布时间】:2016-04-17 08:00:29
【问题描述】:

我在很多测试中使用XCTestExpectation,有时(非常随机地)一些期望没有实现(尽管我确信它们应该是)。

在调查这个问题时,我注意到一些期望在主线程中实现,而一些期望在后台线程中实现。到目前为止,这些问题都是在后台线程中解决的。

满足来自后台线程的期望是否安全?我找不到任何明确的信息。

以下是我如何使用XCTestExpectation的示例:

__block XCTestExpectation *expectation = [self expectationWithDescription:@"test"];

[self doSomethingAsyncInBackgroundWithSuccess:^{
    [expectation fullfill];
}];

[self waitForExpectationsWithTimeout:10.0 handler:^(NSError *error) {
    expectation = nil;
    if (error) {
        NSLog(@"Timeout Error: %@", error);
    }
}];

【问题讨论】:

  • 您确实应该与XCTestExpectation 同步您的交互以避免上述代码中出现竞争条件,但我个人怀疑问题出在其他地方。您可以通过临时添加一些 NSLog 语句来确认这一点并观察时间戳,我怀疑您会发现您的期望超时块确实在调用异步方法的完成块之前被调用。
  • 我真的怀疑从后台线程执行XCTestExpectation 是不安全的。 XCTestExpectations 的重点是运行异步测试。

标签: ios objective-c unit-testing xctest xctestexpectation


【解决方案1】:

XCTestExpectation 是线程安全的并没有在任何地方记录。由于没有关于此事的官方文档,您只能通过创建测试示例来猜测:

- (void)testExpectationMainThread;
{

  __block XCTestExpectation *expectation = [self expectationWithDescription:@"test"];

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    [expectation fulfill];
  });

  [self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
    NSLog(@"%@", error);
  }];

}

- (void)testExpectationStartMainThreadFulfilBackgroundThread;
{

  __block XCTestExpectation *expectation = [self expectationWithDescription:@"test"];

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions), ^{
    [expectation fulfill];
  });

  [self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
    NSLog(@"%@", error);
  }];

}

- (void)testExpectationBackgroundThread;
{
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions);

  __block XCTestExpectation *expectation;


  dispatch_sync(queue, ^{
    expectation = [self expectationWithDescription:@"test"];
  });

  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
    [expectation fulfill];
  });

  [self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
    NSLog(@"%@", error);
  }];

}

在这里它不会崩溃或导致问题,但是由于缺乏官方文档,坚持同一个队列来完成可能更安全。


您确实应该对方法 doSomethingAsyncInBackgroundWithSuccess 进行存根,并为应用程序提供本地“虚拟”数据。

您的单元测试不应依赖网络,因为它是可变的。


您应该在主线程上执行 doSomethingAsyncInBackgroundWithSuccess 的完成块(或至少提供一种在同一线程上一致回调的方法),您可以使用 GCD 轻松完成此操作。

- (void)doSomethingAsyncInBackgroundWithSuccess:(void (^)(void))completion;
{
  dispatch_async(dispatch_get_main_queue(), ^{
    completion();
  });
}

或使用 NSOperationQueue mainQueue

- (void)doSomethingAsyncInBackgroundWithSuccess:(void (^)(void))completion;
{
  [NSOperationQueue.mainQueue addOperationWithBlock:^{
    completion();
  }];
}

【讨论】:

  • 感谢您的回答。实际上,我有问题的期望与网络呼叫无关。这是一个异步操作,但不执行网络操作。我知道我可以切换到主线程,我会试试这个。问题是我不确定是否明确禁止在后台线程中执行,以及这是否是这个问题的根源。我只是猜测这可能是一个不幸的问题。也许您也遇到过类似的问题,而这个派发到 main 解决了这个问题?
  • @Piotr 在 XCTestExpectation 是线程安全的任何地方都没有记录
  • 嗯,你是对的。也许这意味着它不是。我会尝试调度到主线程,希望问题会消失。
  • @Piotr 我添加了一些示例测试用例 - 您可以使用它们来验证您可能对代码提出的任何声明。
猜你喜欢
  • 1970-01-01
  • 2016-11-22
  • 1970-01-01
  • 2014-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多