11

W jaki sposób mogę przetestować mojego delegata NSURLConnection? Utworzono klasę ConnectionDelegate, która jest zgodna z różnymi protokołami służącymi do przesyłania danych z Internetu do różnych kontrolerów ViewController. Zanim przejdę za daleko, chcę rozpocząć pisanie testów jednostkowych. Ale nie wiem, jak przetestować je jako urządzenie bez połączenia z Internetem. Chciałbym również, co powinienem zrobić, aby traktować asynchroniczne wywołania zwrotne.jak przeprowadzić test urządzenia dla delegata NSURLConnection?

Odpowiedz

15

Jest to podobne do reakcji Jona, ale nie mieści się w komentarzu. Pierwszym krokiem jest upewnienie się, że nie tworzysz prawdziwego połączenia. Najłatwiejszym sposobem osiągnięcia tego jest przeciągnięcie procesu tworzenia połączenia do metody fabrycznej, a następnie zastąpienie metody fabryki w teście. Z częściową symulacją OCMocka może to wyglądać tak.

W swojej prawdziwej klasy:

- (NSURLConnection *)newAsynchronousRequest:(NSURLRequest *)request 
{ 
    return [[NSURLConnection alloc] initWithRequest:request delegate:self]; 
} 

W testu:

id objectUnderTest = /* create your object */ 
id partialMock = [OCMockObject partialMockForObject:objectUnderTest]; 
NSURLConnection *dummyUrlConnection = [[NSURLConnection alloc] 
    initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"file:foo"]] 
    delegate:nil startImmediately:NO]; 
[[[partialMock stub] andReturn:dummyUrlConnection] newAsynchronousRequest:[OCMArg any]]; 

Teraz, gdy przedmiot badany próbuje utworzyć połączenie URL faktycznie dostaje atrapę połączenia utworzonego w teście . Fałszywe połączenie nie musi być poprawne, ponieważ nie zaczynamy go i nigdy nie zostanie użyte. Jeśli twój kod używa połączenia, możesz zwrócić kolejną próbę, która kpi z NSURLConnection.

Drugim krokiem jest, aby wywołać metodę na swoim obiekcie, który wyzwala stworzenie NSURLConnection:

[objectUnderTest doRequest]; 

Ponieważ obiekt badany nie używa prawdziwego połączenia możemy wywołać metody delegata od test. Dla NSURLResponse używamy innego makiety, dane odpowiedzi jest tworzony z łańcucha, który jest zdefiniowany gdzieś indziej w teście:

int statusCode = 200; 
id responseMock = [OCMockObject mockForClass:[NSHTTPURLResponse class]]; 
[[[responseMock stub] andReturnValue:OCMOCK_VALUE(statusCode)] statusCode]; 
[objectUnderTest connection:dummyUrlConnection didReceiveResponse:responseMock]; 

NSData *responseData = [RESPONSE_TEXT dataUsingEncoding:NSASCIIStringEncoding]; 
[objectUnderTest connection:dummyUrlConnection didReceiveData:responseData]; 

[objectUnderTest connectionDidFinishLoading:dummyUrlConnection]; 

to wszystko. Efektywnie sfałszowano wszystkie interakcje badanego obiektu z połączeniem, a teraz możesz sprawdzić, czy jest on w stanie, w jakim powinien być.

Jeśli chcesz zobaczyć jakiś "prawdziwy" kod, spójrz na testy dla klasy z projektu CCMenu, który używa NSURLConnections. Jest to trochę mylące, ponieważ testowana klasa nazywa się także połączeniem.

http://ccmenu.svn.sourceforge.net/viewvc/ccmenu/trunk/CCMenuTests/Classes/CCMConnectionTest.m?revision=129&view=markup

+0

Jest to bardzo przydatne. Próbuję rozszerzyć to, aby pokryć rozmowę w przód iw tył, zamiast jednorazowego scenariusza żądania/odpowiedzi, ale nie mam dużo szczęścia. Wysłałem osobne pytanie: http://stackoverflow.com/questions/16472878/how-can-you-use-ocmock-with-nsurlconnection-delegate-for-a-ser-of-network – Gruntcakes

-1

Oto, co zrobić:

  1. Pobierz XAMPP Control Panel http://www.apachefriends.org/en/xampp.html
  2. uruchomić serwer Apache
  3. W folderze ~/Sites, umieścić plik testowy (niezależnie od danych chcesz, zadzwonimy to my file.test).
  4. Uruchom swojego przedstawiciela za pomocą adresu URL: http://localhost/~username/myfile.test
  5. Zatrzymaj serwer apache, gdy go nie używasz.
+1

które ograniczają reakcje na 404, 200 i braku odpowiedzi. A co jeśli chcesz przetestować konflikty 409, 204, 403 i pozostałe kody statusu, etagi, kompresję, itp ... Również, jeśli używasz automatycznego serwera kompilacji, czy zainstalujesz apache i to coś w rodzaju dla każdej maszyny testowej? – Guillermo

+1

Nie jest to metoda, która powinna zostać przekazana jako zalecenie. Guillermo opisuje kilka powodów, dla których całkiem niezły komentarz. Myślę, że powinniśmy zachęcać do lepszych praktyk programistycznych niż to w odpowiedziach dla innych deweloperów ... – Rob

5

Unikaj tworzenia sieci podczas testów jednostkowych. Zamiast tego:

  • Izoluję NSURLConnection w ramach metody.
  • Tworzę podklasę testową, przesłaniając tę ​​metodę, aby usunąć wszystkie ślady NSURLConnection.
  • Piszę jeden test, aby upewnić się, że dana metoda zostanie wywołana, kiedy chcę. Wiem, że to odpali połączenie NSURLC w prawdziwym życiu.

Następnie koncentruję się na bardziej interesującej części: zsyntetyzuj próbki NSURLR o różnych właściwościach i przekaż je do metod NSURLConnectionDelegate.

+0

Co powiesz na drwianie lub unieruchomienie NSURLConnection w moich metodach delegatów? Podkatalogowałem NSURLConnection, aby zawierał odebrane NSData, odbiornik (pobierający JSON, który będę pisać) oraz żądanie użyte do wywołania go. – Moxy

+0

Po obejrzeniu mojego kodu i ponownym przeczytaniu odpowiedzi, widzę, że jestem na dobrej drodze! – Moxy

+0

Moje NSURLConnection jest już odizolowane w metodzie - (void) sendRequest: (NSURLRequest *) request dla odbiorcy: odbiornik (id ). Mogę przetestować wszystkie moje inne metody, aby upewnić się, że adresy URL i żądania są dobrze sformułowane. Z drugiej strony, przez przesłonięcie metody sendRequest mogę przekazywać fałszywe dane, aby przetestować mój odbiornik. Czy to ma sens? – Moxy

5

Moim ulubionym sposobem na to jest podklas NSURL Protococol i udzielanie odpowiedzi na wszystkie żądania HTTP - lub inne protokoły. Następnie zarejestruj protokół testowy w swojej metodzie -setup i wyrejestruje go w swojej metodzie -tearDown. Możesz wtedy mieć ten protokół testowy podawać niektóre dobrze znane dane z powrotem do kodu, aby można było sprawdzić go w swoich testach jednostkowych.

Napisałem kilka artykułów na blogu na ten temat. Najbardziej istotne dla Twojego problemu będą prawdopodobnie Using NSURLProtocol for Injecting Test Data i Unit Testing Asynchronous Network Access.

Możesz również rzucić okiem na mój ILCannedURL Protokół, który jest opisany w poprzednich artykułach. Źródło jest dostępne pod adresem Github.

+0

Czytam oba artykuły i są one naprawdę interesujące. Właściwie to zacząłem otrzymywać pomysły, jak rozwiązać ten problem dzięki nim. Nie sądzę, abym musiał podklasować NSURLProtocol (i wolę nie robić tego na wypadek, gdyby się to zmieniło w przyszłości), ponieważ mój delegat połączenia nie przetwarza danych. Używam mojego odbiornika jako obserwatora. więc przez przesłonięcie metody, która uruchamia połączenie, mogę obsługiwać żądane dane. PS: świetny blog! Mam bookmarked IL;) – Moxy

+0

Dzięki za komentarze. Dla wyjaśnienia, ILCannedURLProtocol nie jest przeznaczony do przejścia do kodu produkcyjnego, więc nie przejmowałbym się zbytnio zmianami w bazowym NSURL Protococol. Jeśli tak się stanie, jest to jedynie zmiana konfiguracji testu. –

+0

[MockNSURLConnection] (https://github.com/wfleming/MockNSURLConnection) działa niesamowicie dobrze z twoim podejściem. – snez

9

EDIT (18.02.2014): Właśnie natknęliśmy się na ten artykuł z bardziej eleganckie rozwiązanie.

http://www.infinite-loop.dk/blog/2011/04/unittesting-asynchronous-network-access/

Zasadniczo masz następujące metody:

- (BOOL)waitForCompletion:(NSTimeInterval)timeoutSecs { 
    NSDate *timeoutDate = [NSDate dateWithTimeIntervalSinceNow:timeoutSecs]; 

    do { 
     [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeoutDate]; 
     if([timeoutDate timeIntervalSinceNow] < 0.0) 
      break; 
    } while (!done); 

    return done; 
} 

Pod koniec swojej metodzie badania, upewnij się rzeczy nie timed out:

STAssertTrue([self waitForCompletion:5.0], @"Timeout"); 

podstawowa format:

- (void)testAsync 
{ 
    // 1. Call method which executes something asynchronously 
    [obj doAsyncOnSuccess:^(id result) { 
     STAssertNotNil(result); 
     done = YES; 
    } 
    onError:^(NSError *error) [ 
     STFail(); 
     done = YES; 
    } 

    // 2. Determine timeout 
    STAssertTrue([self waitForCompletion:5.0], @"Timeout"); 
}  

==============

Spóźniam się na imprezę, ale natknąłem się na bardzo proste rozwiązanie. (Wiele dzięki http://www.cocoabuilder.com/archive/xcode/247124-asynchronous-unit-testing.html)

.h plików:

@property (nonatomic) BOOL isDone; 

pliku .m:

- (void)testAsynchronousMethod 
{ 
    // 1. call method which executes something asynchronously. 

    // 2. let the run loop do its thing and wait until self.isDone == YES 
    self.isDone = NO; 
    NSDate *untilDate; 
    while (!self.isDone) 
    { 
     untilDate = [NSDate dateWithTimeIntervalSinceNow:1.0] 
     [[NSRunLoop currentRunLoop] runUntilDate:untilDate]; 
     NSLog(@"Polling..."); 
    } 

    // 3. test what you want to test 
} 

isDone jest ustawiony na YES w wątku, że metoda asynchroniczna jest wykonywany.

W tym przypadku utworzyłem i uruchomiłem NSURLConnection w kroku 1 i przekazałem delegatowi tę klasę testową.W

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response

ustawić self.isDone = YES;. Wyłamy się z pętli while i test zostanie wykonany. Gotowe.

+0

Awesome. Dałem mi wystarczająco dużo, żeby wszystko działało :) Dzięki. – Johan