Ответ 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:] запускает запрос async и затем разрешает напрямую. Но что, если он сделал больше экзотических вещей, таких как:
+ (void)doThingOnSucceed:onFail: {
[Thing do it];
[self.context performBlock:^{
// Uh oh Farfalle-Os
success();
}];
}
Блок выполнения будет настроен, но вы не дождались его завершения, потому что на самом деле вы не ожидаете внутреннего блока, а только внешнего. Ключ в том, что XCTestWaits фактически позволяет завершить тест, а затем просто проверяет, что обещание было выполнено в течение некоторого периода времени, но в то же время оно начнет выполнение других тестов. Этот успех() может появляться в любом количестве мест и создает любое количество странных поведений.
Поведение изоляции (без изоляции) происходит из-за того, что если вы запускаете только этот тест, все может быть хорошо из-за удачи, но если вы запустите несколько тестов, которые блок 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 создать группу отправки, которую вы можете ждать после каждого теста.