1

mutableCopyiOS で OCMock と GHUnitを使用するための呼び出しをモックしようとしています。

テストに合格したにもかかわらず、EXC_BAD_ACCESSクリーンアップ中に例外が発生しました。その理由を突き止めようとしています。

これを見てください。このテストはmutableCopy、 mockをモックできることを示してい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];
}

ここで、モックの期待値を変更してmutableCopyNSMutableString. テストは引き続き成功しますが、テストを破棄すると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

どちらのテストでも、検証はアサートに関して機能します。これは、両方のテストが適切に構築されており、モックの期待値が期待どおりに起動されていることを示しています。ただし、2 番目のテストは、メモリ アクセスが正しくないためティア ダウンに失敗します。

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 がBasic Memory Management Rulesに従っているという事実によって引き起こされます。具体的にはこれ:

  • あなたが作成したオブジェクトはあなたが所有します

    名前が「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];

次に、適切なタイミングで返されるようにOCMReturnValueProvidera を格納するように呼び出します。copy

@implementation OCMReturnValueProvider

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

この時点で、デバッガーはそれaValueがタイプであると言います__NSCFString。(頭の中で警報ベルが鳴ります。これは、基になる文字列への無料のブリッジではありませんか? への参照ではありませんNSMutableString)

次に、テストが完了して合格します。

ただし、問題OCMReturnValueProviderdealloc'd の場合に発生します。

@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];
}

何が起こっているかというと、コンパイラは によって返されたオブジェクトが であったと想定しているため[string mutableCopy]、is ARCは と同等のことを行います。このオブジェクトの参照カウントが 内で増加していないため、これは問題です。retainedmutableCopyfoodealloced[foo release]andReturn:

によって返されるように構成された他のオブジェクトでこの動作が見られない理由について、私は混乱していますandReturn:OCMReturnValueProviderモックされた応答の処理はARC管理されておらず、戻り値を保持していません。

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

retainしたがって、問題は、返された値を に設定する前にプリエンプティブに ing することで簡単に解決されNSInvocationます。

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

これは のバグのようOCMockです。ただし、すべての状況で問題が発生するわけではないため、正確にはわかりません。私の修正は機能しますが、この余分な必要のないオブジェクトでメモリリークのリスクが発生しますretain。ただし、テストでのメモリ リークと実行されないテストは、今のところ許容範囲内です。

于 2013-03-13T06:40:37.133 に答える