2011-02-03 14 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; 
} 

Używam OCUnit W poniższej metodzie w jaki sposób mogę symulować mój obiekt WebService, lub wynik do metody "startSynchronous", aby móc napisać niezależny test jednostkowy?Cel C - Testowanie jednostkowe i Wyśmiewanie obiektu?

Czy istnieje możliwość wprowadzenia kodu w celu utworzenia fałszywej usługi WWW lub zwrócenia próbnych danych podczas wywołania startSynchronicznego?

Odpowiedz

4

Jednym ze sposobów jest użycie kategorie i zastąpić metody chcesz, można nawet zastąpić metodę init, aby powrócić do atrapa obiektu:

@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 

Problemem jest to, że jeśli chcesz dokonać zwrotu obiektu różne wyniki w różnych testach w jednym pliku testowym nie można tego zrobić. (Można zastąpić każdą metodę raz na stronie testowej)

EDIT:

Jest to stary pytanie napisałem, myślałem, że chciałbym zaktualizować odpowiedzi na pytanie jak napisać kod dającej się przetestować i testów jednostkowych on obecnie:)

Kod 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 

kod testowy

@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

Nie możesz mieć obiektu pojedynczego, takiego jak "TestKonfiguracja", w którym można uzyskać żądane fałszywe wyniki dla każdego połączenia, od w twojej kategorii Mock? –

+0

Możesz odsłonić wszelkie wnętrza jako właściwości, dzięki czemu możesz skonfigurować swoją klasę, jak chcesz. –

1

Opierając się na odpowiedzi WebService od aryaxt, oto mała sztuczka, aby uzyskać różne wyniki w różnych testach.

Po pierwsze, trzeba singleton przedmiot, który będzie używany do przechowywania pożądaną odpowiedź, tuż przed badaniem 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 

Następnie zdefiniowałbyś swoją kategorię "Mock", aby zdefiniować metody próbne, takie jak:

#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 

I wreszcie, w samych testów, byś swizzle mock metodę w konfiguracji i określić oczekiwaną odpowiedź w każdym teście, przed wywołaniem

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

Remark : w moim przykładzie, TestConfiguration używa klasy/selektora jako klucza zamiast obiektu/selektora. Oznacza to, że każdy obiekt klasy użyje tej samej odpowiedzi dla selektora. Jest to najprawdopodobniej twoja sprawa, ponieważ usługa sieciowa jest często pojedyncza. Ale powinien zostać ulepszony do obiektu/selektora, może używając adresu pamięci objet zamiast jego klasy

Powiązane problemy