运行 在 Xcode 9 GM 中进行单元测试时出现神秘的 NSInternalInconsistencyException
Cryptic NSInternalInconsistencyException when running unit tests in Xcode 9 GM
我正在运行在 Xcode 9 GM 上对我的 iOS 应用程序进行单元测试,其中一些失败并出现奇怪的 NSInternalInconsistencyException,抱怨某些测试断言无法被报告,因为涉及的测试没有关联的 XCTestRun 对象。我正在使用 OCMockito + OCHamcrest 进行模拟和调用验证。
出于演示目的,假设我的应用名为 MyTestApp,并且我有一个测试 class FooTest(继承自 XCTestCase)。在 -setUp 中,我为测试创建并连接了各种模拟对象,在 -tearDown 中我将它们设置为 nil 以防万一。
这是我遇到的异常的示例:
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 "infrastructure" classes。
- 受影响的单元测试的其余语句实际上得到执行,直到出现异常为止。
- 隐含的代码行如下所示:
[verify(_mockTestObject) description];
- 堆栈跟踪位于问题发生的位置 - 它说它在 -setUp 中,但据我所知,OCMockito 只是在 next[=35 的开头报告预期失败=]单元测试的运行。问题位置 (FooTest.mm:135) 看起来像上面的 verify() 调用。
事实上,所有因该异常而失败的单元测试都失败了,因为我们试图验证 -[NSObject description]
是否已在一个模拟对象或另一个模拟对象上被调用。删除所有验证该调用的实例使测试通过。
我在 Google 上搜索了此特定 NSInternalInconsistencyException 的其他实例,但没有产生任何结果。我不确定 -[NSObject description] 与可能在模拟对象上调用的任何其他方法有何不同 - 问题可能也不存在,但它只是以这种方式表现出来。我还试图找到是否有任何 debug/diagnostic 选项我可以为 XCTest 打开,这样我可能会获得有关测试执行的更详细的日志记录信息,但我也没有找到类似的东西。
关于我下一步应该去哪里有什么想法吗?非常感谢!
我在处理一些大型遗留代码测试时遇到了类似的问题。问题是:当您 运行 分开时,您对 XCTExpectation
的测试工作正常吗?如果是这样,这意味着一些在 之前 执行的测试 NSInternalInconsistencyException
正在调度 XCTest
相关方法,在相关方法之后执行它们测试结束。
看起来像(示例):
Test1 -> 异步调度 "block" 执行 XCTFail
测试 1 -> 完成
XCTFail
在主线程或其他线程上执行(但 Test1 通过,因为它在没有 "fail" 的情况下完成)。
Test2 -> 用 XCTExpectation
测试一些东西 -> NSInternalInconsistencyException
Apple 文档没有提供太多关于 XCTest 内部结构的信息,但我很确定这就是问题所在。尝试按照故障排除步骤确定测试 "conflicting"(在没有 XCTestExpectation 的情况下执行异步操作的错误测试,例如使用带有完成处理程序的方法最终会执行 XCTest
断言,失败等):
- 在您的套件中进行二进制搜索:禁用一半的测试和 运行 套件与您的
FooTest
。
- 如果您的测试套件 运行 没问题,请重新启用一半已禁用的测试。如果测试套件 运行s 出现异常,则重新启用所有禁用的测试并禁用另一半。
- 然后针对剩余的少量测试重复步骤 1。
- 最后你会得到导致这个异常的测试。
在我的案例中,有多个测试导致了与 XCTestExpectation
的冲突,因此搜索非常麻烦(一套 1000 多个 XCTestCases
需要几个小时,所以大约有 5k 个测试)。
然后彻底调查测试中发生的与您的测试冲突的情况。
可能是 expectation.expectedFulfillmentCount
设置了一个较小的数字。
expectation.expectedFulfillmentCount = numberOfTimesCalled
您的numberOfTimesCalled
可能低于预期的调用次数。例如,您可能会调用两次,但期望设置为调用一次。
我正在运行在 Xcode 9 GM 上对我的 iOS 应用程序进行单元测试,其中一些失败并出现奇怪的 NSInternalInconsistencyException,抱怨某些测试断言无法被报告,因为涉及的测试没有关联的 XCTestRun 对象。我正在使用 OCMockito + OCHamcrest 进行模拟和调用验证。
出于演示目的,假设我的应用名为 MyTestApp,并且我有一个测试 class FooTest(继承自 XCTestCase)。在 -setUp 中,我为测试创建并连接了各种模拟对象,在 -tearDown 中我将它们设置为 nil 以防万一。
这是我遇到的异常的示例:
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 "infrastructure" classes。
- 受影响的单元测试的其余语句实际上得到执行,直到出现异常为止。
- 隐含的代码行如下所示:
[verify(_mockTestObject) description];
- 堆栈跟踪位于问题发生的位置 - 它说它在 -setUp 中,但据我所知,OCMockito 只是在 next[=35 的开头报告预期失败=]单元测试的运行。问题位置 (FooTest.mm:135) 看起来像上面的 verify() 调用。
事实上,所有因该异常而失败的单元测试都失败了,因为我们试图验证 -[NSObject description]
是否已在一个模拟对象或另一个模拟对象上被调用。删除所有验证该调用的实例使测试通过。
我在 Google 上搜索了此特定 NSInternalInconsistencyException 的其他实例,但没有产生任何结果。我不确定 -[NSObject description] 与可能在模拟对象上调用的任何其他方法有何不同 - 问题可能也不存在,但它只是以这种方式表现出来。我还试图找到是否有任何 debug/diagnostic 选项我可以为 XCTest 打开,这样我可能会获得有关测试执行的更详细的日志记录信息,但我也没有找到类似的东西。
关于我下一步应该去哪里有什么想法吗?非常感谢!
我在处理一些大型遗留代码测试时遇到了类似的问题。问题是:当您 运行 分开时,您对 XCTExpectation
的测试工作正常吗?如果是这样,这意味着一些在 之前 执行的测试 NSInternalInconsistencyException
正在调度 XCTest
相关方法,在相关方法之后执行它们测试结束。
看起来像(示例):
Test1 -> 异步调度 "block" 执行 XCTFail
测试 1 -> 完成
XCTFail
在主线程或其他线程上执行(但 Test1 通过,因为它在没有 "fail" 的情况下完成)。
Test2 -> 用 XCTExpectation
测试一些东西 -> NSInternalInconsistencyException
Apple 文档没有提供太多关于 XCTest 内部结构的信息,但我很确定这就是问题所在。尝试按照故障排除步骤确定测试 "conflicting"(在没有 XCTestExpectation 的情况下执行异步操作的错误测试,例如使用带有完成处理程序的方法最终会执行 XCTest
断言,失败等):
- 在您的套件中进行二进制搜索:禁用一半的测试和 运行 套件与您的
FooTest
。 - 如果您的测试套件 运行 没问题,请重新启用一半已禁用的测试。如果测试套件 运行s 出现异常,则重新启用所有禁用的测试并禁用另一半。
- 然后针对剩余的少量测试重复步骤 1。
- 最后你会得到导致这个异常的测试。
在我的案例中,有多个测试导致了与 XCTestExpectation
的冲突,因此搜索非常麻烦(一套 1000 多个 XCTestCases
需要几个小时,所以大约有 5k 个测试)。
然后彻底调查测试中发生的与您的测试冲突的情况。
可能是 expectation.expectedFulfillmentCount
设置了一个较小的数字。
expectation.expectedFulfillmentCount = numberOfTimesCalled
您的numberOfTimesCalled
可能低于预期的调用次数。例如,您可能会调用两次,但期望设置为调用一次。