【问题标题】:Xcode tests pass in isolation, fail when run with other testsXcode 测试单独通过,与其他测试一起运行时失败
【发布时间】:2015-03-20 13:10:36
【问题描述】:

我已经编写了一些带有XCTest 期望的异步单元测试来测试我编写的网络类。我的大部分测试每次都有效。

当我运行整个套件时,有一些测试失败了,但它们自己通过了。

其他测试失败,但使用相同 URL 的请求在粘贴到浏览器时会返回适当的数据。

我的网络代码封装在NSOperation 对象中,这些对象在NSOperationQueue 上运行。 (我的操作队列是默认类型 - 我没有明确地将底层 GCD 队列设置为串行或并发。)

我可以查看哪些内容来修复这些测试?在阅读this post on objc.io 之后,我假设他们遇到了某种隔离问题。

【问题讨论】:

    标签: ios xcode unit-testing nsoperation xctest


    【解决方案1】:

    你走在正确的道路上。 objc.io 文章建议的解决方案可能是正确的方法,但确实需要一些重构。如果您想在进行代码更改狂潮之前将测试作为第一步,那么您可以这样做。

    通常,您可以使用 XCTestExpectations 进行几乎所有的异步测试。标准模式可能是这样的:

    XCTestExpectation *doThingPromise = [self expetationWithDescription:@"Bazingo"];
    [SomeService doThingOnSucceed:^{
      [doThingPromise fulfill];
    } onFail:^ {
    }];
    [self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) {
        expect(error).to.beNil();
    }]
    

    如果 [SomeService doThingOnSucceed:onFail:] 触发异步请求然后直接解析,则此方法可以正常工作。但如果它做一些更奇特的事情会怎样:

    + (void)doThingOnSucceed:onFail: {
      [Thing do it];
      [self.context performBlock:^{
        // Uh oh Farfalle-Os
        success();
      }];
    }  
    

    perform 块将被设置,但您不会等待它完成,因为您实际上并没有在内部块上等待,而只是在外部块上。关键是 XCTestWaits 实际上让测试完成,然后只检查承诺是否在一段时间内实现,但同时它将开始运行其他测试。 success() 可以出现在任意数量的位置并产生任意数量的怪异行为。

    隔离行为(相对于不隔离)来自这样一个事实,即如果您只运行此测试,由于运气,一切可能都很好,但如果您运行多个测试,CoreData 块可能会一直停留,直到下一个测试async,然后将“解锁”其执行,并将在未来某个随机时间开始执行,以进行一些随机的未来测试。

    短期明确的破解方法是暂停您的测试,直到事情完成。这是一个例子:

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [SomeService doThingOnComplete:^{
      dispatch_semaphore_signal(semaphore);
    }];
    while (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_NOW)) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:10]];
    }
    

    这会明确阻止测试在所有内容完成之前完成,这意味着在此测试完成之前无法运行其他测试。

    如果您的测试/代码中有很多此类情况,我会推荐 objc.io 解决方案,即创建一个调度组,您可以在每次测试后等待。

    【讨论】:

    • 尝试实现这个(在 Swift 中),我发现它可以工作,但是一旦信号量发出信号,它就不会继续。它挂在 Runloop.run 上,直到发生超时。这是预期的吗?我的代码:while semaphore.wait(timeout: .now()) == .timedOut { RunLoop.current.run(until: Date(timeIntervalSinceNow: 10)) }
    【解决方案2】:

    在与NSOperationQueue 和看似不正确的waitUntilAllOperationsAreFinished 返回几天后,我想到了一个更简单的选择:将您的测试划分为多个测试目标。这为您的测试提供了自己的“应用程序”环境,更重要的是,在这种情况下,确保 Xcode/XCUnit 将按顺序运行它们,这样它们就不会相互干扰——除非它们执行诸如使数据库变脏(这可能应该是无论如何都是失败的)。

    执行此操作的最快方法是复制您的测试目标,从原始目标中删除失败的测试,然后从新目标中删除除失败测试之外的所有内容。请注意,如果您有多个测试相互干扰,那么您需要多个目标来实现足够的隔离。

    您可以通过检查测试目标方案来检查测试是否已执行。在该方案中,您应该看到两个(全部)测试目标以及在它们下面的各个测试。

    【讨论】:

      【解决方案3】:

      我在使用Publisher 运行单元测试时遇到了这种情况,该Publisher 遇到了实时 API(我稍后会模拟)太快并且速率受限。这是我想出的解决方法。

      1. 在测试类中,我声明了一个DispatchQueue 属性,如下所示:
          let testQueue = DispatchQueue(label: "Test Queue", qos: .default)
          let delay = DispatchQueue.SchedulerTimeType.Stride(1.0)
      
      
      1. 然后,在每个测试方法中,我在发布者声明之后添加了以下内容:
      .delay(for: delay, scheduler: testQueue)
      

      我最终在测试通过之前一直玩延迟。

      【讨论】:

        猜你喜欢
        • 2015-05-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-10-13
        • 1970-01-01
        相关资源
        最近更新 更多