2011-02-03 5 views
5
- (BOOL)coolMethod:(NSString*)str 
{ 
    //do some stuff 
    Webservice *ws = [[WebService alloc] init]; 
    NSString *result = [ws startSynchronous:url]; 
    if ([result isEqual:@"Something"]) 
    { 
     //More calculation 
     return YES; 
    } 
    return NO; 
} 

나는 OCUnit 을 사용하고 있습니다. 다음 메소드에서 어떻게 WebService Object 또는 결과를 "startSynchronous"메소드로 조종하여 독립적 인 유닛 테스트를 작성할 수 있습니까?목표 C - 단위 테스트 및 모의 객체?

모의 웹 서비스를 만들거나 startSynchronous 콜에서 모의 ​​데이터를 반환하려면 코드를 삽입 할 수 있습니까?

답변

4

한 가지 방법은 당신도 모의 개체를 반환 init 메소드를 오버라이드 (override) 할 수 범주를 사용하여 원하는 메소드를 오버라이드 (override)하는 것입니다

@interface Webservice (Mock) 
- (id)init; 
@end 

@implementation Webservice (Mock) 
- (id)init 
{ 
    //WebServiceMock is a subclass of WebService 
    WebServiceMock *moc = [[WebServiceMock alloc] init]; 
    return (Webservice*)moc; 
} 
@end 

이 가진 문제는 당신이 객체의 수익을 만들고 싶어 1 개의 테스트 파일에서 다른 테스트의 결과가 다르면 그렇게 할 수 없습니다.

이 내가 게시 오래된 질문은, 내가 내가 쓸만한 코드와 단위 테스트를 요즘를 작성하는 방법에 대한 답을 업데이트 할 것이라고 생각 :

가 EDIT (당신은 테스트 페이지 당 한 번씩 각 메소드를 오버라이드 (override) 할 수 있습니다))

의 ViewController 코드

@implementation MyViewController 
@synthesize webService; 

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    [self.webService sendSomeMessage:@"Some_Message"]; 
} 

- (WebService *)webService 
{ 
    if (!_webService) 
     _webService = [[WebService alloc] init]; 

    return _webService; 
} 

@end 

테스트 코드

@implementation MyViewControllerTest 

- (void)testCorrectMessageIsSentToServer 
{ 
    MyViewController *vc = [[MyViewController alloc] init]; 
    vc.webService = [OCMock niceMockForClass:[WebService class]]; 

    [[(OCMockObject *)vc.webService expect] [email protected]"Some_Message"]; 
    [vc view]; /* triggers viewDidLoad */ 
    [[(OCMockObject *)vc.webService verify]; 
} 

@end 
+0

각 호출에 대해 원하는 가짜 결과를 얻을 수있는 "TestConfiguration"과 같은 싱글 톤 객체를 가질 수 없습니다. 귀하의 모의 범주 내에서? –

+0

모든 내부를 속성으로 표시 할 수 있으므로 원하는대로 클래스를 구성 할 수 있습니다. –

1

aryaxt의 WebService 응답 맨 위에 다른 테스트에서 다른 결과를 얻을 수있는 약간의 트릭이 있습니다. 먼저

, 당신은 바로 테스트 TestConfiguration.h

#import <Foundation/Foundation.h> 
#import <objc/runtime.h> 
#import <objc/message.h> 


void MethodSwizzle(Class c, SEL orig, SEL new); 

@interface TestConfiguration : NSObject 


@property(nonatomic,strong) NSMutableDictionary *results; 

+ (TestConfiguration *)sharedInstance; 


-(void)setNextResult:(NSObject *)result 
    forCallToObject:(NSObject *)object 
       selector:(SEL)selector; 


-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector; 
@end 

TestConfiguration.m

#import "TestConfiguration.h" 


void MethodSwizzle(Class c, SEL orig, SEL new) { 
    Method origMethod = class_getInstanceMethod(c, orig); 
    Method newMethod = class_getInstanceMethod(c, new); 
    if(class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) 
     class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); 
    else 
     method_exchangeImplementations(origMethod, newMethod); 
}; 

@implementation TestConfiguration 


- (id)init 
{ 
    self = [super init]; 
    if (self) { 
     self.results = [[NSMutableDictionary alloc] init]; 
    } 
    return self; 
} 

+ (TestConfiguration *)sharedInstance 
{ 
    static TestConfiguration *sharedInstance = nil; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     sharedInstance = [[TestConfiguration alloc] init]; 
     // Do any other initialisation stuff here 
    }); 
    return sharedInstance; 
} 


-(void)setNextResult:(NSObject *)result 
    forCallToObject:(NSObject *)object 
      selector:(SEL)selector 
{ 
    NSString *className = NSStringFromClass([object class]); 
    NSString *selectorName = NSStringFromSelector(selector); 

    [self.results setObject:result 
        forKey:[[className stringByAppendingString:@":"] stringByAppendingString:selectorName]]; 
} 

-(NSObject *)getResultForCallToObject:(NSObject *)object selector:(SEL)selector 
{ 
    NSString *className = NSStringFromClass([object class]); 
    NSString *selectorName = NSStringFromSelector(selector); 

    return [self.results objectForKey:[[className stringByAppendingString:@":"] stringByAppendingString:selectorName]]; 

} 



@end 

전에, 원하는 답을 저장하는 데 사용됩니다 싱글 객체가 필요 그런 다음 모의 메서드를 정의하기 위해 "모의"범주를 정의합니다.

,
#import "MyWebService+Mock.h" 
#import "TestConfiguration.h" 

@implementation MyWebService (Mock) 


-(void)mockFetchEntityWithId:(NSNumber *)entityId 
          success:(void (^)(Entity *entity))success 
          failure:(void (^)(NSError *error))failure 
{ 

    Entity *response = (Entity *)[[TestConfiguration sharedInstance] getResultForCallToObject:self selector:@selector(fetchEntityWithId:success:failure:)]; 

    if (response == nil) 
    { 
     failure([NSError errorWithDomain:@"entity not found" code:1 userInfo:nil]); 
    } 
    else{ 
     success(response); 
    } 
} 

@end 

그리고 마지막으로는, 테스트 자체, 당신은 호출

MyServiceTest.m

- (void)setUp 
{ 
    [super setUp]; 

    //swizzle webservice method call to mock object call 
    MethodSwizzle([MyWebService class], @selector(fetchEntityWithId:success:failure:), @selector(mockFetchEntityWithId:success:failure:)); 
} 

- (void)testWSMockedEntity 
{ 
    /* mock an entity response from the server */ 
    [[TestConfiguration sharedInstance] setNextResult:[Entity entityWithId:1] 
             forCallToObject:[MyWebService sharedInstance] 
               selector:@selector(fetchEntityWithId:success:failure:)]; 

    // now perform the call. You should be able to call STAssert in the blocks directly, since the success/error block should now be called completely synchronously. 
} 

비고 전에 설정에서 모의 ​​방법을 스위 즐링 (swizzle), 각 시험에서 예상 답을 정의 할 : 내 예제에서, TestConfiguration 클래스/셀렉터 키 대신 개체/선택기를 사용합니다. 즉, 클래스의 모든 객체가 선택기에 대해 동일한 대답을 사용하게됩니다. 웹 서비스가 종종 싱글 톤이기 때문에 그럴 가능성이 큽니다. 하지만 객체/선택기로 클래스 대신 objet의 메모리 주소를 사용해야합니다.