我编写了一些具有XCTest
期望的异步单元测试,以测试我编写的网络类。我的大多数测试每次都有效。
当我运行整个套件时,有一些测试会失败,但会自行通过。
其他测试失败,但是将具有相同URL的请求粘贴到浏览器时会返回适当的数据。
我的网络代码封装在NSOperation
中运行的NSOperationQueue
对象中。 (我的操作队列是默认类型-我尚未将底层GCD队列显式设置为串行或并发。)
我该如何看待这些测试?阅读this post on objc.io之后,我假设它们正在遭受某种隔离问题。
您走在正确的道路上。 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块可能会一直停留到下一个异步测试为止,然后它将“解除阻止”其执行,并且它将在某个随机的将来时间开始执行,以进行一些随机的未来测试。
短期的明确修改是暂停测试,直到一切完成。这是一个例子:
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解决方案。
在与NSOperationQueue
战斗并且从waitUntilAllOperationsAreFinished
看似不正确的返回几天后,我遇到了一个更简单的选择:将测试分为多个测试目标。这为您的测试提供了自己的“应用程序”环境,更重要的是,在这种情况下,确保Xcode / XCUnit可以按顺序运行它们,以使它们不会相互干扰-除非它们进行了使数据库变脏的工作(可能应该无论如何还是失败)。
最快的方法是复制测试目标,从原始目标中删除失败的测试,并从新目标中删除所有失败的测试(除了失败的测试之外)。请注意,如果您有多个相互干扰的测试,则许多都需要多个目标才能实现足够的隔离。
<< img src =“ https://image.soinside.com/eyJ1cmwiOiAiaHR0cHM6Ly9pLnN0YWNrLmltZ3VyLmNvbS9QYk9oSi5wbmcifQ==” alt =“额外目标”>
您可以通过检查测试目标方案来检查测试是否已执行。在方案中,您应该同时看到两个(全部)测试目标,并在其下方分别进行测试。
[我在使用Publisher
运行单元测试时遇到了这个问题,该测试太快地击中了实时API(稍后将进行模拟),并且速率受到限制。这是我想出的解决方法。
DispatchQueue
属性,如下所示: let testQueue = DispatchQueue(label: "Test Queue", qos: .default)
let delay = DispatchQueue.SchedulerTimeType.Stride(1.0)
.delay(for: delay, scheduler: testQueue)
我最终延迟了测试,直到测试通过。