在部分模拟的对象上调用 + [NSBundle bundleForClass:] 返回的结果与未模拟的对象不同?

Posted

技术标签:

【中文标题】在部分模拟的对象上调用 + [NSBundle bundleForClass:] 返回的结果与未模拟的对象不同?【英文标题】:Calling + [NSBundle bundleForClass:] on a partially mocked object is returning a different result than the unmocked object? 【发布时间】:2013-09-26 13:51:59 【问题描述】:

我正在使用 XCTest 和 OCMock 2.2.1 进行单元测试。我有一个类使用以下方法获取包标识符:

NSString *bundleIdentifier = [[NSBundle bundleForClass:[self class]] bundleIdentifier];

这在运行应用程序或特别是针对此类的单元测试时按预期工作。

在对其他类进行测试时,我部分地模拟了这个对象,但仍然需要获取包标识符的方法才能运行。

我看到的是在将对象实例传递给 + [OCMockObject partialMockForObject:] 之前看起来是正确的:

(lldb) po myObject
<MyObject: 0x1006ec480>
(lldb) po [NSBundle bundleForClass:[myObject class]]
NSBundle </Users/paynerc/Library/Developer/Xcode/DerivedData/xxxx/Build/Products/Debug/MyTests Tests.xctest> (loaded)
(lldb) po [[NSBundle bundleForClass:[myObject class]] bundleIdentifier]
com.paynerc.MyBundle

但是,在我将myObject 传递到[OCMockObject partialMockForObject:myObject] 之后,情况发生了变化:

(lldb) po myObject
<MyObject-0x1006ec480-401894396.880136: 0x1006ec480>
(lldb) po [NSBundle bundleForClass:[myObject class]]
NSBundle </Applications/Xcode.app/Contents/Developer/usr/bin> (loaded)
(lldb) po [[NSBundle bundleForClass:[myObject class]] bundleIdentifier]
nil

对象被修改并包含部分模拟魔术的事实是有道理的。似乎没有意义的是为什么对 bundleForClass 的调用改变了它返回的内容。

除了模拟 MyObject 中的调用之外,我能做些什么来确保 bundleForClass 继续返回原始值吗?令人担忧的是,任何其他需要在另一个单元测试中部分模拟 MyObject 的人都需要记住提供 bundleForClass 的存根实现。

我目前的解决方案是请求捆绑标识符并检查结果。如果它是 nil,我将调用 [NSBundle allBundles] 并遍历它们,直到找到一个具有非 nil 的 bundleIdentifier。虽然目前......工作......它是 A)不是很健壮 B)可怕的蛮力和 C)修改应用程序代码以支持单元测试。

有没有其他人遇到过这个问题并提出了更好的解决方案?

【问题讨论】:

【参考方案1】:

运行时运行正常。模拟对象是 NSProxy 的子类,因此,对象的 isa 和捆绑包之间的运行时绑定被有效破坏(特别是,isa 指向 Class,然后通过 dyld 查找API 来确定从中加载它的 mach-o 图像, 用于查找捆绑包)。

OCMockObject 代理(或子类 OCPartialMockObject)上可能有 API,可让您检索原始类。当然,您必须使用那些只应在测试中使用的模拟调用来污染您的代码。

或者,在您的 bundle/framework/whatever 中的一个类上实现一个类方法,该类返回该类的 bundle。这不应该被嘲笑。

【讨论】:

比尔像往常一样是对的。但是,我们正在更改模拟对象的行为,以便它可以满足您的期望。这是正在进行的工作。见github.com/erikdoe/ocmock/pull/45 和github.com/erikdoe/ocmock/commit/… NSProxy 子类通常实现 -isKindOfClass: 为被代理的类返回 YES,OCClassMockObject 也不例外。 (虽然它可能也应该实现 -class ,因为 NSProxy 子类通常也这样做)。但这里的问题在于原始对象本身被模拟——部分模拟过程改变了它的“isa”指针,因此 -class 方法的返回值不同,这反过来又搞乱了 bundleForClass:。但正如 Erik 所说,看起来这应该在 OCMock 的下一个版本中修复。 @CarlLindberg 感谢您的澄清;我之前没有看过 OCMock* 的实现。

以上是关于在部分模拟的对象上调用 + [NSBundle bundleForClass:] 返回的结果与未模拟的对象不同?的主要内容,如果未能解决你的问题,请参考以下文章

[NSBundle mainBundle] 声明的 CFString 对象

在为自定义视图调用 NSBundle(使用 loadNibName)后访问 IBOutlets 的最佳方法

有没有办法使用 EasyMock 部分模拟对象?

google mock - 我可以在同一个模拟对象上多次调用 EXPECT_CALL 吗?

MonoTouch - 无法在包中加载 NIB:“NSBundle”

Mock 模拟测试简介及 Mockito 使用入门