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
Несколько примечательных примечаний:
- Модульные тесты не инициализируют XCTestCase или любые классы инфраструктуры XCTest вне рамок XCTest.
- Остальные утверждения затронутых модульных тестов фактически выполняются, вплоть до исключения.
- Введенная строка кода выглядит так:
[verify(_mockTestObject) description];
- Трассировка стека лежит там, где возникает проблема - она говорит, что она находится в -setUp, но из того, что я могу сказать, OCMockito просто сообщает о ожиданиях ожидания в начале next unit test запустить. Расположение проблемы (FooTest.mm:135) выглядит как вышеупомянутый вызов verify().
Фактически, все модульные тесты, которые выходят из строя с этим исключением, терпят неудачу, потому что мы пытаемся проверить, что -[NSObject description]
был вызван одним макетным объектом или другим. Удаление всех экземпляров проверки этого вызова позволяет пройти тесты.
Я искал в Google другие экземпляры этого исключительного исключения NSInternalInconsistencyException и не дал никаких результатов. Я не уверен, каким образом [описание NSObject] отличается от любого другого метода, который может быть вызван на макет объекта - возможно, что проблема тоже не существует, но это просто проявляется именно так. Я также пытался найти, есть ли какие-либо опции отладки/диагностики, которые я могу включить для XCTest, поэтому я мог бы получить более подробную информацию о протоколировании выполнения теста, но я тоже ничего не нашел.
Любые идеи для того, где я должен смотреть дальше? Большое спасибо!
Ответы
Ответ 1
У меня возникла аналогичная проблема при работе над некоторым большим набором устаревших тестов кода. Вопрос в том, работают ли ваши тесты при XCTExpectation
отлично, когда вы запускаете их отдельно? Если это так, это означает, что некоторые из тестов, которые выполняются перед вашими испытаниями с помощью NSInternalInconsistencyException
, отправляют связанные методы XCTest
таким образом, который их выполняет после завершения соответствующего теста.
он выглядит как (пример):
Test1 → отправляет асинхронно "блок", который выполняет XCTFail
Test1 → завершает
XCTFail
выполнено (но Test1 проходит, по завершении без "fail" ) в основном или другом потоке.
Test2 → проверяет что-то с XCTExpectation
→ NSInternalInconsistencyException
В документах Apple не содержится много информации о внутренних настройках XCTest, но я уверен, что это проблема. Попробуйте выполнить следующие действия по устранению неполадок, чтобы привязать тесты "конфликтующие" (плохие, которые делают асинхронные элементы без XCTestExpectation, например методы использования с обработчиками завершения, которые в конечном итоге выполняют утверждения XCTest
, не работают и т.д.):
- Сделайте бинарный поиск в своем пакете: отключите половину тестов и запустите пакет с помощью
FooTest
.
- Если в вашем тестовом наборе выполняется повторная активация половины отключенных тестов. Если тестовый пакет работает с исключением, повторно включил все отключенные тесты и отключил другую половину.
- Повторите шаг 1, после чего осталось меньшее количество тестов.
- Наконец, вы получите тесты, которые вызывают это исключение.
В моем случае было несколько тестов, вызвавших этот конфликт с XCTestExpectation
, поэтому поиск был довольно неприятным (несколько часов для набора из 1000+ XCTestCases
, поэтому около 5k тестов).
Затем тщательно исследуйте, что происходит в тесте, противоречащем вашему тесту.
Ответ 2
Это может быть expectation.expectedFulfillmentCount
с меньшим номером.
expectation.expectedFulfillmentCount = numberOfTimesCalled
Ваш numberOfTimesCalled
может быть меньше, чем количество numberOfTimesCalled
ожидания. Например, вы можете звонить дважды, но ожидание устанавливается так, как оно будет вызвано один раз.