1

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

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

看看这个。该测试表明可以在 mockmutableCopy上进行模拟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))

你能告诉我为什么会发生吗?

谢谢,乔

4

3 回答 3

2

您面临的问题是由 ARC 遵循基本内存管理规则这一事实引起的。具体来说:

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

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

所以解决方案是查看调用选择器来确定retain是否returnValue

我希望这会有所帮助。

于 2013-03-14T08:42:18.643 回答
0

我是了解正在发生的事情的一部分。我已经为自己编译了一个调试库,OCMock以便我可以了解崩溃发生的位置。

这是我发现的。

在我最初的测试中,我调用andReturn:设置返回期望值:

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

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

@implementation OCMReturnValueProvider

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

此时调试器说那aValue是 type __NSCFString。(警钟在我脑海中响起;这不是通往底层字符串的免费桥梁吗?不是对 的引用NSMutableString

接下来测试完成并通过。

但是,现在当'd时OCMReturnValueProvider会出现问题。dealloc

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

[returnValue release]调用时发生崩溃;正在OCMReturnValueProvider尝试发布__NSCFStringretain之前编辑的内容。

接下来,我打开了 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]

因此,测试类中的某些内容导致发布多于保留。为什么会这样?奇怪的!

于 2013-03-12T12:23:33.140 回答
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];
}

正在发生的事情是编译器假设 is 返回的对象[string mutableCopy]retainedby mutableCopy,所以当foois deallocedARC 相当于[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。但是,测试中的内存泄漏与未运行的测试相比,目前对我来说是可以接受的。

于 2013-03-13T06:40:37.133 回答