10

の下に、プロパティを持つARCオブジェクトがあります。のいくつかのテストを作成しようとしており、 を使用してそのプロパティをモックしています。ChildweakparentChildparentOCMock

ARCではNSProxy、合成された弱いプロパティセッターを使用してサブクラスを設定してもプロパティは設定されません...弱いプロパティが設定された後の行で、それをチェックすると、それがすでにnil. 具体例は次のとおりです。

@interface Child : NSObject
@property (nonatomic, weak) id <ParentInterface>parent;
@end

@implementation Child
@synthesize parent = parent_;
@end

//  ... later, inside a test class ...

- (void)testParentExists
{
    // `mockForProtocol` returns an `NSProxy` subclass
    //
    OCMockObject *aParent = [OCMockObject mockForProtocol:@protocol(ParentInterface)];
    assertThat(aParent, notNilValue());

    // `Child` is the class under test
    //
    Child *child = [[Child alloc] init];
    assertThat(child, notNilValue());

    assertThat(child.parent, nilValue());
    child.parent = (id<ParentInterface>)aParent;
    assertThat([child parent], notNilValue());  // <-- This assertion fails
    [aParent self]; // <-- Added this reference just to ensure `aParent` was valid until the end of the test.
}

がを参照するためにassignプロパティの代わりにプロパティを使用してこれを回避できることはわかっていますが、それを使い終わったら (ある種の穴居人のように)をアウトする必要があります。 ARCが回避するはずだったもの。weakChildParentnilparent

アプリのコードを変更せずにこのテストに合格する方法について何か提案はありますか?

編集: であることと関係OCMockObjectがあるようです。 のインスタンスにNSProxyすると、弱参照は nil 以外の値を「保持」します。アプリのコードを変更せずにこのテストに合格する方法をまだ探しています。aParentNSObjectchild.parent

編集 2 :ブレイクの答えを受け入れた後、条件付きでプロパティを弱いから変更するプリプロセッサ マクロのプロジェクトで実装を行いました -> 割り当て。あなたのマイレージは異なる場合があります:

#if __has_feature(objc_arc)
#define BBE_WEAK_PROPERTY(type, name) @property (weak, nonatomic) type name
#else
#define BBE_WEAK_PROPERTY(type, name) @property (assign, nonatomic) type name
#endif
4

3 に答える 3

9

私たちはこれと同じ問題に取り組んできましたが、実際には、ARC と NSProxy 派生オブジェクトへの弱い参照との間の非互換性に関係しています。OCMock を介してテストできるように、プリプロセッサ ディレクティブを使用して弱いデリゲート参照を条件付きでコンパイルし、テスト スイート内で割り当てることをお勧めします。

于 2012-03-12T18:12:19.993 に答える
6

コードを変更できないコードをテストしていたので、条件付きマクロとは異なる解決策を見つけました。

NSProxyではなくNSObjectを拡張する単純なクラスを作成しました。これは、すべてのセレクター呼び出しをOCMockProxyに転送します。

CCWeakMockProxy.h:

#import <Foundation/Foundation.h>

/**
 * This class is a hack around the fact that ARC weak references are immediately nil'd if the referent is an NSProxy
 * See: http://stackoverflow.com/questions/9104544/how-can-i-get-ocmock-under-arc-to-stop-nilling-an-nsproxy-subclass-set-using-a-w
 */
@interface CCWeakMockProxy : NSObject

@property (strong, nonatomic) id mock;

- (id)initWithMock:(id)mockObj;

+ (id)mockForClass:(Class)aClass;
+ (id)mockForProtocol:(Protocol *)aProtocol;
+ (id)niceMockForClass:(Class)aClass;
+ (id)niceMockForProtocol:(Protocol *)aProtocol;
+ (id)observerMock;
+ (id)partialMockForObject:(NSObject *)anObject;

@end

CCWeakMockProxy.m:

#import "CCWeakMockProxy.h"
#import <OCMock/OCMock.h>


#pragma mark Implementation
@implementation CCWeakMockProxy

#pragma mark Properties
@synthesize mock;

#pragma mark Memory Management
- (id)initWithMock:(id)mockObj {
    if (self = [super init]) {
        self.mock = mockObj;
    }
    return self;
}

#pragma mark NSObject
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.mock;
}

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [self.mock respondsToSelector:aSelector];
}

#pragma mark Public Methods
+ (id)mockForClass:(Class)aClass {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject mockForClass:aClass]];
}

+ (id)mockForProtocol:(Protocol *)aProtocol {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject mockForProtocol:aProtocol]];
}

+ (id)niceMockForClass:(Class)aClass {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject niceMockForClass:aClass]];
}

+ (id)niceMockForProtocol:(Protocol *)aProtocol {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject niceMockForProtocol:aProtocol]];
}

+ (id)observerMock {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject observerMock]];
}

+ (id)partialMockForObject:(NSObject *)anObject {
    return [[CCWeakMockProxy alloc] initWithMock:[OCMockObject partialMockForObject:anObject]];
}

@end

通常のOCMockObjectと同じように、結果のオブジェクトを使用するだけです。

于 2012-07-27T18:59:05.643 に答える
0

もちろん。をnil割り当てた直後にchild.parent、プロキシオブジェクト自体がテストによって解放され(参照されなくなったため)、これにより弱い参照がゼロになるためです。したがって、解決策は、テスト中にプロキシ オブジェクトを維持することです。への呼び出しを挿入することで、これを簡単に行うことができます

[aParent self];

メソッドの最後に。その関数呼び出しは何もしません (-self単に を返しますself) が、ARC がオブジェクトを有効に保つことを保証します。

別の方法は、宣言を be に変更するaParentことです__autoreleasing。これにより、変数がスコープ外になったときにオブジェクトを明示的に解放するのではなく、ARC が自動解放された参照をそのスロットに残すだけで、MRR のように動作します。あなたはそれを行うことができます

__autoreleasing OCMockObject *aParent = ...

そうは言っても、テスト中にオブジェクトを明示的に存続させているため、最初の解決策はおそらくよりクリーンです。

于 2012-02-01T23:13:54.897 に答える