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

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

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

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

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

Следует отметить несколько моментов:

  • The unit tests are not initializing XCTestCase or any XCTest "infrastructure" classes outside of the XCTest framework.
  • The remaining statements of the affected unit tests actually gets executed, up to the point of the exception.
  • The implicated line of code looks like: [verify(_mockTestObject) description];
  • The stack trace is lying about where the problem occurs - it says that it's in -setUp, but from what I can tell, OCMockito simply reports expectation failures at the beginning of the next unit test's run. The problem location (FooTest.mm:135) looks like the above verify() call.

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

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

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


person RuslanD    schedule 19.09.2017    source источник
comment
Получение той же ошибки с UITesting Xcode при запуске XCTestCase (). WaitForExpectations   -  person Charlie Seligman    schedule 21.09.2017
comment
@CharlieSeligman Вы добились в этом прогресса? Тем временем я попробовал кое-что - во-первых, я обновился до последних версий OCMockito / OCHamcrest, но по-прежнему получаю эту ошибку, поэтому маловероятно, что это связано со старыми зависимостями. Во-вторых, используя Xcode 8.3.3, я обнаружил, что даже если я изменил вызовы verify (), чтобы использовать неправильное количество вызовов, тесты все равно пройдут, что означает, что даже в Xcode 8 эти вызовы не работали должным образом, но они работали. так тихо.   -  person RuslanD    schedule 22.09.2017
comment
@RusianD - Закончил переработку метода, так что waitForExpectations больше не вызывается ... так что не исправлено, боюсь.   -  person Charlie Seligman    schedule 22.09.2017


Ответы (2)


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

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

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

Test1 -> финиши

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

Test2 -> проверяет что-то с XCTExpectation -> NSInternalInconsistencyException

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

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

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

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

person Wladek Surala    schedule 27.11.2017

Это может быть меньшее значение expectation.expectedFulfillmentCount.

expectation.expectedFulfillmentCount = numberOfTimesCalled

Ваш numberOfTimesCalled может быть меньше, чем количество вызовов ожидания. Например, вы можете звонить дважды, но ожидание установлено так, как если бы оно было вызвано один раз.

person Amadeu Cavalcante Filho    schedule 17.04.2019