Cryptic NSInternalInconsistencyException при запуске модульных тестов в Xcode 9 GM


Я запускаю модульные тесты моего iOS-приложения на Xcode 9 GM, и несколько из них терпят неудачу со странным NSInternalInconsistencyException, жалуясь, что некоторые утверждения тестов не могут быть сообщены, потому что вовлеченные тесты не имеют связанного объекта XCTestRun. Я использую OCMockito + OCHamcrest для насмешек и проверки звонков.

Для демонстрации предположим, что мое приложение называется MyTestApp, и у меня есть тестовый класс FooTest (который наследуется от XCTestCase). В -установки, я создаю и соедините проволокой различные макеты объектов для испытаний, и в процессе демонтажа я установил их на ноль на всякий случай.

Вот пример исключения, которое я получаю:

2017-09-19 13:23:01.852729-0700 xctest[17006:5392130] *** Assertion failure in -[FooTest recordFailureWithDescription:inFile:atLine:expected:], /Library/Caches/com.apple.xbs/Sources/XCTest_Sim/XCTest-13201/Sources/XCTestFramework/Core/XCTestCase.m:308
/Users/fooUser/Workspaces/MyTestApp/tests/FooTest.mm:46: error: -[FooTest testSomething] : failed: caught "NSInternalInconsistencyException", "Unable to report test assertion failure 'Expected 1 matching invocation, but received 0' from /Users/fooUser/Workspaces/MyTestApp/tests/FooTest.mm:135 because it was raised inside test case -[FooTest testSomethingElse] which has no associated XCTestRun object. This may happen when test cases are constructed and invoked independently of standard XCTest infrastructure."
(
    0   CoreFoundation                      0x000000010255d1cb __exceptionPreprocess + 171
    1   libobjc.A.dylib                     0x0000000101ebff41 objc_exception_throw + 48
    2   CoreFoundation                      0x0000000102562362 +[NSException raise:format:arguments:] + 98
    3   Foundation                          0x0000000101827089 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 193
    4   XCTest                              0x0000000101d96875 -[XCTestCase recordFailureWithDescription:inFile:atLine:expected:] + 518
    5   MyAppUnitTests                     0x000000011d7f2f5a -[HCXCTestFailureReporter executeHandlingOfFailure:] + 154
    6   MyAppUnitTests                     0x000000011d7f2b73 -[HCTestFailureReporter handleFailure:] + 67
    7   MyAppUnitTests                     0x000000011d64ec9a MKTFailTest + 217
    8   MyAppUnitTests                     0x000000011d64c3f4 -[MKTExactTimes verifyData:] + 254
    9   MyAppUnitTests                     0x000000011d64f2ba -[MKTBaseMockObject verifyInvocation:usingVerificationMode:] + 137
    10  MyAppUnitTests                     0x000000011d64f20b -[MKTBaseMockObject handlingVerifyOfInvocation:] + 115
    11  MyAppUnitTests                     0x000000011d64f15a -[MKTBaseMockObject forwardInvocation:] + 64
    12  CoreFoundation                      0x00000001024dfed8 ___forwarding___ + 760
    13  CoreFoundation                      0x00000001024dfb58 _CF_forwarding_prep_0 + 120
    14  MyAppUnitTests                     0x000000011c40b486 -[FooTest setUp] + 294
    15  XCTest                              0x0000000101d97b39 __24-[XCTestCase invokeTest]_block_invoke_3 + 31
    16  XCTest                              0x0000000101d97809 __24-[XCTestCase invokeTest]_block_invoke + 271
    17  XCTest                              0x0000000101ddff45 -[XCUITestContext performInScope:] + 183
    18  XCTest                              0x0000000101d976ef -[XCTestCase invokeTest] + 141

Несколько примечательных моментов:

    Модульные тесты не инициализируют xctestcase или любые классы "инфраструктуры" XCTest вне рамок XCTest.
  • остальные операторы затронутых модульных тестов фактически выполняются, вплоть до момента исключения.
  • В причастная строка кода выглядит следующим образом: [verify(_mockTestObject) description];
  • трассировка стека лежит там, где возникает проблема - она говорит, что она находится в настройке, но из того, что я могу сказать, OCMockito просто сообщает о сбоях ожидания в началеследующего запуска модульного теста. Проблемное место (FooTest. mm: 135) выглядит так же, как и приведенный выше вызов verify ().

На самом деле, все модульные тесты, которые терпят неудачу с этим исключением, терпят неудачу, потому что мы пытаемся проверить, что -[NSObject description] был вызван на одном макете объект или другой. Удаление всех экземпляров проверки этого вызова делает тесты проходными.

Я искал в Google другие экземпляры этого конкретного NSInternalInconsistencyException, и это не дало никаких результатов. Я не уверен в том, каким образом- [NSObject description] отличается от любого другого метода, который может быть вызван на макет объекта - возможно, что проблема на самом деле не существует, но она просто проявляется таким образом. Я также пытался выяснить, есть ли они. параметры отладки / диагностики я могу включить для XCTest, чтобы, возможно, получить более подробную информацию о выполнении теста, но я также не нашел ничего подобного.

Есть идеи, где я должен искать дальше? Большое спасибо!

1 10

1 ответ:

Я столкнулся с подобной проблемой во время работы над большим набором тестов устаревшего кода. Вопрос в том, хорошо ли работают ваши тесты с XCTExpectation, когда вы запускаете их отдельно? Если это так, это означает, что некоторые тесты, выполняемые перед вашими тестами с NSInternalInconsistencyException, отправляют XCTest связанные методы таким образом, что они выполняются после завершения соответствующего теста.

Это выглядит так (пример):

Test1 - > отправляет асинхронно "блок", который выполняет XCTFail

Test1 - > finishes

XCTFail выполняется (но Test1 проходит, так как он закончился без "fail") на основном или другом потоке.

Test2 - > проверяет что-то с помощью XCTExpectation -> NSInternalInconsistencyException

Документы Apple не предоставляют много информации о внутренних внутренностях XCTest, но я уверен, что это проблема. Попробуйте выполнить следующие шаги по устранению неполадок, чтобы определить тесты "конфликтующие" (плохие, которые делают асинхронные вещи без XCTestExpectation, например, использовать методы с обработчики завершения, которые в конечном счете делают утверждения XCTest, терпят неудачу и т. д.):

  1. выполните двоичный поиск в вашем наборе: отключите половину тестов и запустите набор с вашим FooTest.
  2. Если ваш набор тестов работает нормально, повторно включите половину отключенных тестов. Если набор тестов выполняется с исключением, повторно включите все отключенные тесты и отключите другую половину.
  3. затем повторите шаг 1 относительно меньшего количества оставшихся тестов.
  4. В конце концов вы получите тесты, которые вызывают это исключение.

В моем случае было несколько тестов, вызывающих этот конфликт с XCTestExpectation, поэтому поиск был довольно утомительным (несколько часов для набора из 1000+ XCTestCases, так что около 5K тестов).

Затем тщательно исследуйте, что происходит в тесте, который противоречит вашему тесту.