Stubbing/wyśmiewanie webservices dla aplikacji na iOS

Pracuję nad aplikacją na iOS, której głównym celem jest komunikacja z zestawem zdalnych usług internetowych. W przypadku testów integracyjnych chciałbym móc uruchomić moją aplikację przeciwko jakimś fałszywym usługom internetowym, które mają przewidywalny wynik.

Do tej pory widziałem dwie propozycje:

  1. Utwórz serwer WWW, który serwuje klientowi wyniki statyczne (na przykład tutaj).
  2. zaimplementować inny kod komunikacji webservice, który na podstawie znacznika czasu kompilacji wywołałby webservices lub kod, który ładowałby odpowiedzi z lokalnego pliku (przykład i kolejny ).

Jestem ciekaw, co społeczność sądzi o każdym z tych podejść i czy istnieją jakieś narzędzia wspierające ten przepływ pracy.

Update: podam więc konkretny przykład. Mam formularz logowania, który przyjmuje nazwę użytkownika i hasło. Chciałbym sprawdzić dwa warunki:

  1. [email protected] getting login denied i
  2. [email protected] logowanie się pomyślnie.

Więc potrzebuję kodu, aby sprawdzić parametr username i rzucić odpowiednią odpowiedź na mnie. Mam nadzieję, że to wszystko, czego potrzebuję w "fałszywej webservice". Jak sobie z tym poradzić?

Author: Community, 2012-05-30

5 answers

Jeśli chodzi o opcję 1, robiłem to w przeszłości używając CocoaHTTPServer i osadzając serwer bezpośrednio w teście OCUnit:

Https://github.com/robbiehanson/CocoaHTTPServer

Umieściłem kod do użycia tego w teście jednostkowym tutaj: https://github.com/quellish/UnitTestHTTPServer

W końcu HTTP jest z założenia tylko request/response.

Wyśmiewanie usługi internetowej, tworzenie makiety serwera HTTP lub tworzenie makiety usługi internetowej w kodzie, będzie mniej więcej tyle samo pracy. Jeśli masz ścieżki kodu X do przetestowania, masz co najmniej ścieżki kodu X do obsługi w makiecie.

W przypadku opcji 2, aby wyśmiewać usługę sieciową, nie komunikujesz się z nią, zamiast tego używasz obiektu, który ma znane odpowiedzi. [MyCoolWebService performLogin:username withPassword:password]

Stanie się, w Twoim teście

[MyMockWebService performLogin:username withPassword:password] Najważniejsze jest to, że MyCoolWebService i MyMockWebService realizują tę samą umowę (w objective-c byłoby to Protokół). OCMock ma mnóstwo dokumentacji, która pomoże Ci zacząć.

Dla testu integracji, powinien być testowanie pod kątem rzeczywistych usług internetowych, takich jak QA / staging środowiska. To, co tak naprawdę opisujesz, brzmi bardziej jak testy funkcjonalne niż testy integracyjne.

 2
Author: quellish,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-08 06:36:51

Proponuję użyć Nocilla . Nocilla jest biblioteką do stubowania żądań HTTP za pomocą prostego DSL.

Powiedzmy, że chcesz zwrócić 404 z google.com. wszystko co musisz zrobić to:

stubRequest(@"GET", "http://www.google.com").andReturn(404); // Yes, it's ObjC

Potem dowolny HTTP do google.com zwróci 404.

Bardziej kompletny przykład, w którym chcesz dopasować POST z określonym ciałem i nagłówkami i zwrócić puszkę odpowiedzi:

stubRequest(@"POST", @"https://api.example.com/dogs.json").
withHeaders(@{@"Accept": @"application/json", @"X-CUSTOM-HEADER": @"abcf2fbc6abgf"}).
withBody(@"{\"name\":\"foo\"}").
andReturn(201).
withHeaders(@{@"Content-Type": @"application/json"}).
withBody(@"{\"ok\":true}");
Możesz dopasować każde żądanie i sfałszować każdą odpowiedź. Sprawdź README, aby dowiedzieć się więcej szczegóły. Zalety stosowania Nocilla w porównaniu z innymi rozwiązaniami to:]}
    Jest szybki. Brak serwerów HTTP do uruchomienia. Twoje testy przebiegną bardzo szybko.
  • brak szalonych zależności do zarządzania. Do tego możesz użyć Kokon.
  • Jest dobrze przetestowany.
  • świetny DSL, który sprawi, że Twój kod będzie naprawdę łatwy do zrozumienia i utrzymania.

Głównym ograniczeniem jest to, że działa tylko z frameworkami HTTP zbudowanymi na NSURLConnection, jak AFNetworking, MKNetworkKit lub zwykły NSURLConnection.

Mam nadzieję, że to pomoże. Jeśli będziesz czegoś potrzebował, pomogę ci.
 25
Author: luisobo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-28 03:16:07

Zakładam, że używasz Objective-C. Dla Objective-C OCMock jest szeroko stosowany do wyśmiewania/testów jednostkowych (twoja druga opcja).

[21]}ostatni raz korzystałem z OCMock ponad rok temu, ale o ile pamiętam jest to pełnoprawny framework do szydzenia i może robić wszystkie rzeczy, które są opisane poniżej.

Ważną rzeczą w mockach jest to, że możesz używać tak dużo lub tak mało rzeczywistej funkcjonalności swoich obiektów. Można utworzyć "pustą" makietę (która wszystkie metody są twoim obiektem, ale nic nie zrobią) i nadpisują tylko te metody, których potrzebujesz w swoim teście. Zwykle odbywa się to podczas testowania innych obiektów, które opierają się na makiecie.

Możesz też utworzyć makietę, która będzie działać tak, jak zachowuje się Twój rzeczywisty obiekt, i wypisać niektóre metody, których nie chcesz testować na tym poziomie (np. - metody, które faktycznie uzyskują dostęp do bazy danych, wymagają połączenia z siecią, itp.). Zwykle robi się to podczas testowania wyśmiewanego obiektu siebie.

Ważne jest, aby zrozumieć, że nie tworzysz kpin raz na zawsze. Każdy test może tworzyć mocki dla tych samych obiektów na nowo w oparciu o to, co jest testowane.

Kolejną ważną rzeczą w mockach jest to, że możesz "nagrywać" scenarious (sekwencje połączeń) i swoje "oczekiwania" na ich temat (które metody za kulisami powinny być wywoływane, z jakimi parametrami i w jakiej kolejności), a następnie "odtwarzać" scenariusz - test nie powiedzie się, jeśli oczekiwania nie zostaną spełnione met. Jest to główna różnica między klasycznym a mockistowskim TDD. Ma swoje plusy i minusy (patrz artykuł Martina Fowlera).

Rozważmy teraz twój konkretny przykład (będę używał pseudo-składni, która wygląda bardziej jak C++ lub Java, a nie Objective C): {]}

Załóżmy, że masz obiekt klasy LoginForm, który reprezentuje wprowadzone dane logowania. Posiada (m.in.) metody setName(String),setPassword(String), bool authenticateUser(), i Authenticator* getAuthenticator().

Masz również obiekt klasy Authenticator, który ma (m.in.) metody bool isRegistered(String user), bool authenticate(String user, String password), i bool isAuthenticated(String user).

Oto jak możesz przetestować kilka prostych scenariuszy:]}

Utwórz MockLoginForm mock ze wszystkimi metodami pustymi z wyjątkiem czterech wymienionych powyżej. Pierwsze trzy metody będą wykorzystywały rzeczywistą implementację LoginForm; getAuthenticator() zostaną usunięte, aby zwrócić MockAuthenticator.

Utwórz makietę, która użyje fałszywej bazy danych (takiej jak wewnętrzna struktura danych lub plik) do implementacji swoich trzech metod. Baza danych będzie zawierać tylko jeden krotka: ('rightuser','rightpassword').

TestUserNotRegistered

Scenariusz powtórki:

MockLoginForm.setName('wronuser');
MockLoginForm.setPassword('foo');
MockLoginForm.authenticate();

Oczekiwania:

getAuthenticator() is called
MockAuthenticator.isRegistered('wrognuser') is called and returns 'false'

TestWrongPassword

Scenariusz powtórki:

MockLoginForm.setName('rightuser');
MockLoginForm.setPassword('foo');
MockLoginForm.authenticate();

Oczekiwania:

getAuthenticator() is called
MockAuthenticator.isRegistered('rightuser') is called and returns 'true'
MockAuthenticator.authenticate('rightuser','foo') is called and returns 'false'

TestLoginOk

Scenariusz powtórki:

MockLoginForm.setName('rightuser');
MockLoginForm.setPassword('rightpassword');
MockLoginForm.authenticate();
result = MockAuthenticator.isAuthenticated('rightuser')

Oczekiwania:

getAuthenticator() is called
MockAuthenticator.isRegistered('rightuser') is called and returns 'true'
MockAuthenticator.authenticate('rightuser','rightpassword') is called and returns 'true'
result is 'true'

Mam nadzieję, że to pomoże.
 7
Author: malenkiy_scot,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-05 20:56:48

Możesz zrobić makietę serwisu www całkiem skutecznie z podklasą NSURLProtocol:

Nagłówek:

@interface MyMockWebServiceURLProtocol : NSURLProtocol
@end

Realizacja:

@implementation MyMockWebServiceURLProtocol

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    return [[[request URL] scheme] isEqualToString:@"mymock"];
}

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
    return request;
}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
    return [[a URL] isEqual:[b URL]];
}

- (void)startLoading
{
    NSURLRequest *request = [self request];
    id <NSURLProtocolClient> client = [self client];
    NSURL *url = request.URL;
    NSString *host = url.host;
    NSString *path = url.path;
    NSString *mockResultPath = nil;
    /* set mockResultPath here … */
    NSString *fileURL = [[NSBundle mainBundle] URLForResource:mockResultPath withExtension:nil];
    [client URLProtocol:self
 wasRedirectedToRequest:[NSURLRequest requestWithURL:fileURL]
       redirectResponse:[[NSURLResponse alloc] initWithURL:url
                                                  MIMEType:@"application/json"
                                     expectedContentLength:0
                                          textEncodingName:nil]];
    [client URLProtocolDidFinishLoading:self];
}

- (void)stopLoading
{
}

@end

Interesującą procedurą jest-startLoading, w której należy przetworzyć żądanie i zlokalizować plik statyczny odpowiadający odpowiedzi w pakiecie aplikacji przed przekierowaniem klienta na adres URL tego pliku.

Instalujesz protokół z

[NSURLProtocol registerClass:[MyMockWebServiceURLProtocol class]];

I odwoływać się do niego z adresami URL jak

mymock://mockhost/mockpath?mockquery

To jest znacznie prostsze niż wdrożenie prawdziwego webservice na zdalnym komputerze lub lokalnie w aplikacji; kompromis polega na tym, że symulowanie nagłówków odpowiedzi HTTP jest znacznie trudniejsze.

 6
Author: Phil Willoughby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-06-11 12:06:36

OHTTPStubs to całkiem świetny framework do robienia tego, co chcesz, który zyskał dużą przyczepność. Z ich github readme:

OHTTPStubs jest biblioteką zaprojektowaną do łatwego stubowania żądań sieciowych. Może Ci pomóc:

    [11]} Przetestuj aplikacje z fałszywymi danymi sieciowymi (stubowanymi z pliku) i symuluj powolne sieci, aby sprawdzić zachowanie aplikacji w złych warunkach sieciowych [12]}
  • napisz testy jednostkowe, które wykorzystują fałszywe dane sieciowe z twojego Osprzęt.

Działa z NSURLConnection, nowym iOS7 / OSX.9 ' S NSURLSession, AFNetworking (Oba 1.x i 2.x) lub dowolnego frameworka sieciowego używającego systemu ładowania adresów URL Cocoa.

Nagłówki OHHTTPStubs są w pełni udokumentowane za pomocą komentarzy podobnych do Appledoc / Headerdoc w plikach nagłówkowych. możesz również przeczytać dokumentację online tutaj .

Oto przykład:

[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
    return [request.URL.host isEqualToString:@"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse*(NSURLRequest *request) {
    // Stub it with our "wsresponse.json" stub file
    NSString* fixture = OHPathForFileInBundle(@"wsresponse.json",nil);
    return [OHHTTPStubsResponse responseWithFileAtPath:fixture
              statusCode:200 headers:@{@"Content-Type":@"text/json"}];
}];

Możesz znaleźć dodatkowe przykłady użycia na stronie wiki .

 6
Author: memmons,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-22 18:24:36