Тесты Xcode проходят изолированно, сбой при запуске с другими тестами

Я написал несколько асинхронных модульных тестов с ожиданиями XCTest для тестирования сетевого класса, который я написал. Большинство моих тестов работают каждый раз.

Есть несколько тестов, которые терпят неудачу, когда я запускаю весь пакет, но передаю их самостоятельно.

Другие тесты терпят неудачу, но запросы с одинаковыми URL-адресами возвращают соответствующие данные при вставке в браузер.

Мой код сети инкапсулирован в объекты NSOperation, которые запускаются на NSOperationQueue. (Моя операционная очередь - это тип по умолчанию - я не задал явную установку базовой очереди GCD как последовательную или параллельную.)

Что я могу проверить, чтобы исправить эти тесты? Прочитав этот пост на objc.io, я предполагаю, что они страдают от какой-то изолированной проблемы.

Ответ 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 создать группу отправки, которую вы можете ждать после каждого теста.

Ответ 2

После боя NSOperationQueue и, казалось бы, неправильного возврата из waitUntilAllOperationsAreFinished в течение нескольких дней я столкнулся с более простым вариантом: разделите свои тесты на несколько тестовых целей. Это дает вашим испытаниям собственную среду "приложения" и, что более важно, в этом случае гарантирует, что Xcode/XCUnit будет запускать их последовательно, чтобы они не могли вмешиваться друг в друга - если они не делают такие вещи, как уход из базы данных, грязный (что, вероятно, должно быть отказ в любом случае).

Самый быстрый способ сделать это - дублировать тестовую цель, удалить неудачные тесты из оригинала и удалить все, кроме ваших неудачных тестов, из новой цели. Обратите внимание, что если у вас есть несколько тестов, мешающих друг другу, многим нужно много целей для достижения достаточной изоляции.

Extra targets

Вы можете проверить, что тесты выполняются путем проверки целевой тестовой схемы. В схеме вы должны увидеть как (все) вас тестовые цели, так и под ними свои индивидуальные тесты.

Scheme editor