2009-07-06 3 views
6

이 문제를 테스트하는 방법에 대해 궁금합니다. 나는 매개 변수를 취하는 메서드를 가지고 있으며,이 매개 변수의 일부 속성을 기반으로 다른 개체를 만들고이를 조작합니다. 코드는 다음과 같이 보입니다 :Objective-C의 메서드 내부 개체를 어떻게 테스트 할 수 있습니까?

- (void) navigate:(NavContext *)context { 
    Destination * dest = [[Destination alloc] initWithContext:context]; 
    if (context.isValid) { 
    [dest doSomething]; 
    } else { 
    // something else 
    } 
    [dest release]; 
} 

내가 뭘 확인하려는 것은 context.isValid에 해당하는 경우, 그 해봐요은 이명 령에 호출 될 것입니다,하지만 그조차 있다면 나는 그것을 테스트하는 방법을 알고 (또는하지 않습니다 가능한 한) 객체가 메소드의 범위 내에서 완전히 생성되기 때문에 OCMock 또는 다른 전통적인 테스트 메소드를 사용한다. 내가 잘못된 방향으로가는거야?

답변

5

(당신이 당신의 목표는 당신의 단위 테스트를 통지 한 후 doSomething에 대한 호출에 전달 하나 DestinationdoSomething 방법을 대체 할 것이다 달성하기 위해, 한 가지 방법이 테스트를 할. 인 경우)

당신은 할 수 OCMock을 사용하지만 코드를 수정하여 Destination 객체를 사용하거나 모의 객체로 먼저 대체 할 수있는 싱글 톤 객체를 사용해야합니다.

아마

-(void) navigate:(NavContext *)context destination:(Destination *)dest; 

방법을 구현하는 것이 작업을 수행하는 가장 깨끗한 방법. 다음에 -(void) navigate:(NavContext *)context의 구현을 변경 :

- (void) navigate:(NavContext *)context { 
    Destination * dest = [[Destination alloc] initWithContext:context]; 
    [self navigate:context destination:dest]; 
    [dest release]; 
} 

이것은 당신의 테스트를 직접 추가 매개 변수와 메소드를 호출 할 수 있습니다. (다른 언어에서는 destination 매개 변수의 기본값을 제공하는 것으로 간단히 구현할 수 있지만 Objective-C는 기본 매개 변수를 지원하지 않습니다.)

+0

이것은 가장 쉬운 대답 인 것 같지만 대상 객체를 여러 범위에 살게하는 것이 조금 복잡해 보입니다. 쉬운 테스트를 지원하기 위해 다른 곳에서는 필요하지 않습니다. 나는 타협이 어딘가에 만들어 질 필요가 있다고 생각한다 : – Kevlar

+0

그래; 별도의 Destination 객체를 대체 할 수 있기를 원한다면이 객체를 전달하는 몇 가지 방법이 있어야합니다. 이것이 내가 알고있는 가장 깨끗한 방법입니다. –

0

method swizzling과 같은 흥미로운 기술을 사용하면 완전히 가능하지만 잘못된 방식 일 것입니다. 단위 테스트에서 doSomething을 호출하는 효과를 관찰 할 수있는 방법이 전혀 없다면 구현 세부 사항을 doSomething 호출한다는 사실이 아닙니까?

+0

내가 메서드가 호출되는지 테스트하지 않으면 큰 문제는 아니 겠지. 아직 TDD/유닛 테스팅에 익숙하지 않아서 모범 사례를 아직 모릅니다. :) – Kevlar

1

내가 확인하고자하는 것은 context.isValid가 true 인 경우입니다. 그 doSomething은 dest에 호출됩니다.

여기서는 잘못된 것을 테스트했을 것입니다. 당신은 ObjC에서 부울 문이 올바르게 작동한다고 가정합니다. Context 객체를 대신 테스트 하시겠습니까? context.isValid를 사용하면 [dest doSomething] 분기가 실행됩니다.

+0

이 테스트에서는 어떻게 생성되는지 상관하지 않기 때문에 컨텍스트 개체를 조롱하고 있습니다.이 메서드는 컨텍스트의 속성을 기반으로 올바른 작업을 수행합니다. – Kevlar

+0

또한, navigate : 구현이 변경되면 그는 여전히 doSomething이 호출되는지 확인할 수 있습니다. –

+0

아, 충분히 공평하게, 나는 조금 짧게 관측되고 있었다. 나는 BJ Homer의 접근 방식을 좋아합니다. – EightyEight

0

저는이 상황에서 공장 방법을 사용하고 싶습니다.

id destinationMock = [OCMock mockForClass:FakeDestination.class]; 
// do the swizzle 
[FakeDestination setSharedInstance:destinationMock]; 
[[destinationMock expect] doSomething]; 
// Call your method 
[destinationMock verify]; 
:이 설정되면

#import "Destination+Factory.h" 

@interface FakeDestination : Destination 
+ (id)sharedInstance; 
+ (void)setSharedInstance:(id)sharedInstance; 
// Note! Instance method! 
- (Destination *)destinationWithContext:(NavContext *)context; 
@end 

@implementation FakeDestination 
+ (id)sharedInstance 
{ 
    static id _sharedInstance = nil; 
    if (!_sharedInstance) 
    { 
     _sharedInstance = [[FakeDestination alloc] init]; 
    } 
    return _sharedInstance; 
} 
+ (void)setSharedInstance:(id)sharedInstance 
{ 
    _sharedInstance = sharedInstance; 
} 
// Overrides 
+ (Destination *)destinationWithContext:(NavContext *)context { [FakeDestination.sharedInstance destinationWithContext:context]; } 
// Instance 
- (Destination *)destinationWithContext:(NavContext *)context { return nil; } 
@end 

, 당신은 당신이 설정하고 지금 + (Destination *)destinationWithContext:(NavContext *)context;

에 대한 swizzle the class methods해야합니다

@interface Destination(Factory) 
+ (Destination *)destinationWithContext:(NavContext *)context; 
@end 

@implementation Destination(Factory) 
+ (Destination *)destinationWithContext:(NavContext *)context 
{ 
    return [[Destination alloc] initWithContext:context]; 
} 
@end 

나는 다음 FakeClass을

이것은 코딩의 상당한 양이지만 앞에 재사용 할 수 있습니다.