10

ARC 아래에 weak 속성 인 parent이있는 Child 개체가 있습니다. Child에 대한 몇 가지 테스트를 작성하려고하고 있는데 OCMock을 사용하여 parent 속성을 조롱하고 있습니다.약한 속성을 사용하여 NSProxy 서브 클래스 집합을 초기화하는 것을 중지하도록 ARC에서 OCMock을 얻으려면 어떻게해야합니까?

ARC에서 합성 된 약한 속성 설정자를 사용하여 NSProxy 하위 클래스를 설정하면 약한 속성을 설정 한 후 속성 ... 행을 설정하지 않고 이미 nil임을 알 수 있습니다. 여기에 구체적인 예입니다 : 나는 내가 Parent를 참조 할 Child에 대한 weak 특성 대신 assign 속성을 사용하여이 문제를 해결받을 수 있다는 사실을 알고

@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. 
} 

, 그러나 나는 parent에서 nil해야 할 것이다 때 (일종의 원시인과 같이) ARC가 제거해야했던 것과 정확히 일치하는 방식으로 수행되었습니다.

내 앱 코드를 변경하지 않고이 테스트를 통과하는 방법에 대한 제안이 있으십니까?

편집 : 그것은 내가 aParentNSObject의 인스턴스 만들 경우 OCMockObjectNSProxy 인으로해야 할 것으로 보인다는 child.parent 약한 참조는 nil이 아닌 값을 "보유". 앱 코드를 변경하지 않고이 테스트를 통과 할 수있는 방법을 찾고 있습니다.

편집 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 
+0

다음 커밋 메시지를 확인하십시오. https://github.com/erikdoe/ocmock/commit/dbdb233ae84498077f7e946abb49731968333f0b OCMock 팀이 동일한 문제를 겪고있는 것처럼 보입니다. – Don

답변

9

우리는이 동일한 문제로 어려움을 겪었으며 실제로 ARC와 NSProxy에서 파생 된 개체에 대한 약한 참조 사이의 비 호환성과 관련이 있습니다. 전처리 지시문을 사용하여 약한 대리자 참조를 조건부로 컴파일하여 OCMock을 통해 테스트 할 수 있도록 테스트 스위트 내에서 할당하는 것이 좋습니다.

0

확실히. 을 할당 한 직후 프록시 객체 자체가 테스트에 의해 해제되므로 (더 이상 참조되지 않기 때문에) nil이 될 것이므로 약한 참조가 없음으로 바뀝니다. 따라서 해결책은 테스트 중에 프록시 객체를 활성 상태로 유지하는 것입니다. 방법을 끝내고

에 전화를 걸어서 간단하게 처리 할 수 ​​있습니다. 그 함수 호출은 아무 것도하지 않고 (-self은 단지 self을 반환합니다), ARC가 객체를 안전하게 유지합니다.

또 다른 변수가 범위를 벗어나면 그냥 명시 적으로 개체를 해제하는 대신 해당 슬롯에 오토 릴리즈 참조를 떠나 그 ARC 더 MRR처럼 행동하게하는, __autoreleasing을 할 aParent 당신의 선언을 변경하는 것 .

__autoreleasing OCMockObject *aParent = ... 

그런데 첫 번째 해결 방법은 테스트 중에 개체를 명시 적으로 활성 상태로 유지하기 때문일 수 있습니다.

+0

내 테스트의 하단에 제안한 참조를 추가하고 작동하지 않는다고 확인했습니다. 'parent'는 여전히 테스트의 하단에서 유효하지만,'child.parent'는 항상'nil'입니다. – Prairiedogg

+0

@Prairiedogg : 아마도 테스트가 예상대로 진행되고 있을까요? OCMock은 약한 참조와 호환되지 않습니다 (retain/release를 재정의하면 발생할 수 있음). –

+0

'__autoreleasing'을 추가해도 작동하지 않는 것으로 보입니다. 테스트 범위의 'aParent'에 대한 참조가 테스트 메소드의 마지막 줄까지 유효하다는 것을 확인할 수 있지만 합성 된'child' 속성은 결코 설정되지 않습니다. 항상'nil'입니다. – Prairiedogg

6

내가 코드를 변경할 수없는 코드를 테스트 했으므로 조건부 매크로와 다른 해결책을 발견했습니다.

모든 선택기 호출을 OCMockProxy에 전달하는 NSProxy가 아닌 NSObject를 확장하는 간단한 클래스를 작성했습니다.

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처럼 결과 객체를 사용!