使用OCMock意外崩溃,在NSString上模拟'mutableCopy'

时间:2013-03-11 18:30:47

标签: ios ocmock gh-unit

我正试图在iOS上使用OCMock和GHUnit模拟对mutableCopy的调用。

尽管测试通过,但在清理过程中我遇到了EXC_BAD_ACCESS异常,我正试图找出原因。

看看这个。此测试表明可以在模拟mutableCopy上模拟NSString。在此测试中,我返回另一个NSString,而不是NSMutableString。这只是为了证明mutableCopy期望被触发,测试通过。

#import <GHUnitIOS/GHUnit.h>
#import "OCMock.h"

@interface TestItClass : GHTestCase @end
@implementation TestItClass

// Test that mutableCopy on an NSString is mockable.
- (void)test_1_mutableCopyOfString_shouldBeMockable_givenAStringIsReturned {
    NSString *string = [OCMockObject mockForClass:NSString.class];
    NSString *copy = @"foo";
    [(NSString *) [[(id) string expect] andReturn:copy] mutableCopy];

    // MutableCopy is mocked to return a string, not a mutable string!
    // This is clearly wrong from a static typing point of view, but
    // the test passes anyway, which is ok.
    NSMutableString *result = [string mutableCopy];
    GHAssertEquals(result, copy, nil);
    [(id)string verify];
}

现在我更改模拟期望值,以便mutableCopy现在返回NSMutableString。测试仍然通过,但是在测试的推断下我得到EXC_BAD_ACCESS例外。

- (void)test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned {
    NSString *string = [OCMockObject mockForClass:NSString.class];
    NSMutableString *copy = [@"foo" mutableCopy];
    [(NSString *) [[(id) string expect] andReturn:copy] mutableCopy];

    // Now mutableCopy is mocked to return a mutable string!
    // The test now blows up during the test teardown! Why?
    NSMutableString *foo = [string mutableCopy];
    GHAssertEquals(foo, copy, nil);
    [(id)string verify];
}

@end

在两个测试中,验证工作,以及断言。这表明两个测试都构造良好,模拟期望正如预期的那样被激发。但是,第二次测试在内存访问不良的情况下失败了:

Simulator session started with process 7496
Debugger attached to process 7496
2013-03-11 18:23:05.519 UnitTests[7496:c07] TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned ✘ 0.00s
2013-03-11 18:23:06.466 UnitTests[7496:c07] Re-running: TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned <GHTest: 0x7793340>
Exception: EXC_BAD_ACCESS (code=1, address=0x11dfe3ea))

你能告诉我为什么会这样吗?

谢谢, 乔

3 个答案:

答案 0 :(得分:2)

您面临的问题是ARC跟随Basic Memory Management Rules的事实。特别是:

  
      
  • 您拥有自己创建的任何对象

         

    使用名称以“alloc”,“new”,“copy”或“mutableCopy”开头的方法创建对象(例如,alloc,newObject或mutableCopy)。

  •   

因此,解决方案是查看调用选择器以确定是否retain returnValue

我希望这有帮助。

答案 1 :(得分:0)

我是理解正在发生的事情的一部分。我自己编译了一个OCMock的调试库,这样我就能理解发生崩溃的位置。

这是我发现的。

在我的原始测试中,我调用andReturn:来设置返回期望值:

NSMutableString *copy = [@"foo" mutableCopy];
[(NSString *) [[(id) string expect] andReturn:copy] mutableCopy];

这反过来调用OCMReturnValueProvider来存储copy,以便它可以在适当的时间返回:

@implementation OCMReturnValueProvider

- (id)initWithValue:(id)aValue
{
    self = [super init];
    returnValue = [aValue retain];
    return self;
}

此时调试器说aValue的类型为__NSCFString。 (警报铃声在我脑海中响起;不是一个免费的桥接到一个基础字符串?不是对NSMutableString的引用)

接下来测试完成并通过。

但是,当OCMReturnValueProviderdealloc'时,问题就出现了。

@implementation OCMReturnValueProvider
- (void)dealloc
{
    [returnValue release];
    [super dealloc];
}

当调用[returnValue release]时发生崩溃; OCMReturnValueProvider正试图释放__NSCFString之前的retain

接下来,我打开了NSZombie调试,这显示了:

2013-03-12 20:58:19.654 UnitTests[16667:c07] TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned  
2013-03-12 20:58:21.778 UnitTests[16667:c07] Re-running: TestItClass/test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned <GHTest: 0x4afc5fd0>
2013-03-12 20:58:21.780 UnitTests[16667:c07] *** -[CFString release]: message sent to deallocated instance 0x4b0b1fe0

malloc-history(Find Zombie乐器)正在帮助阐明它:

Category            Event Type  Ref Ct  Responsible Caller
CFString (mutable)  Malloc        1     -[TestItClass test_2_mutable...]
CFString (mutable)  Retain        2     -[OCMReturnValueProvider initWithValue:]
CFString (mutable)  Retain        3     -[TestItClass test_2_mutable...]
CFString (mutable)  Retain        4     -[TestItClass test_2_mutable...]
CFString (mutable)  Release       3     -[TestItClass test_2_mutable...]
CFString (mutable)  Release       2     -[TestItClass test_2_mutable...]
CFString (mutable)  Release       1     -[TestItClass test_2_mutable...]
CFString (mutable)  Release       0     -[TestItClass test_2_mutable...]
CFString (mutable)  Zombie       -1     -[OCMReturnValueProvider dealloc]

因此,测试类中的某些内容会导致更多的版本而不是保留。为什么会这样?奇怪!

答案 2 :(得分:0)

经过一番调查后,我发现了崩溃的原因。

让我们再看一下测试:

- (void)test_2_mutableCopyOfString_shouldBeMockable_givenAMutableStringIsReturned {
    NSString *string = [OCMockObject mockForClass:NSString.class];
    NSMutableString *copy = [@"foo" mutableCopy];
    [(NSString *) [[(id) string expect] andReturn:copy] mutableCopy];

    NSMutableString *foo = [string mutableCopy];
}

正在发生的事情是编译器假设[string mutableCopy]返回的对象是retained mutableCopyfoodealloced ARC相当于[foo release]。这是一个问题,因为我们这个对象的引用计数没有在andReturn:内增加。

我很困惑为什么我们没有看到配置为由andReturn:返回的其他对象的这种行为。处理模拟响应的OCMReturnValueProvider未受ARC管理,并且未保留返回的值:

- (void)handleInvocation:(NSInvocation *)anInvocation
{
    [anInvocation setReturnValue:&returnValue];
}

因此,在retain中设置返回值之前,先通过先验NSInvocation来解决问题:

- (void)handleInvocation:(NSInvocation *)anInvocation
{
    [returnValue retain];
    [anInvocation setReturnValue:&returnValue];
}

这看起来像OCMock中的错误。但鉴于在所有情况下都不会出现这个问题,我不确定。我的修复工作正常,但现在冒着在可能不需要额外retain的对象上泄漏内存的风险。但是,测试中的内存泄漏与未运行的测试相比,我现在都可以接受。