Stubbing / burlarse de los servicios web para una aplicación iOS


Estoy trabajando en una aplicación iOS cuyo propósito principal es la comunicación con un conjunto de servicios web remotos. Para las pruebas de integración, me gustaría poder ejecutar mi aplicación contra algún tipo de servicios web falsos que tengan un resultado predecible.

Hasta ahora he visto dos sugerencias:

  1. Cree un servidor web que sirva resultados estáticos al cliente (por ejemplo aquí).
  2. Implementar un código de comunicación webservice diferente, que basado en un indicador de tiempo de compilación llamaría ya sea servicios web o código que cargaría respuestas desde un archivo local (ejemplo y otro).

Tengo curiosidad por saber qué piensa la comunidad acerca de cada uno de estos enfoques y si hay herramientas para apoyar este flujo de trabajo.

Update: Permítanme dar un ejemplo específico entonces. Tengo un formulario de inicio de sesión que toma un nombre de usuario y contraseña. Me gustaría comprobar dos condiciones:

  1. [email protected] consiguiendo inicio de sesión denegado y
  2. [email protected] iniciando sesión correctamente.

Así que necesito algún código para comprobar el parámetro username y lanzarme una respuesta apropiada. Espero que esa sea toda la lógica que necesito en el "servicio web falso". ¿Cómo manejo esto limpiamente?

Author: Community, 2012-05-30

5 answers

En cuanto a la opción 1, he hecho esto en el pasado usando CocoaHTTPServer e incrustando el servidor directamente en una prueba de OCUnit:

Https://github.com/robbiehanson/CocoaHTTPServer

Pongo el código para usar esto en una prueba unitaria aquí: https://github.com/quellish/UnitTestHTTPServer

Después de todo, HTTP es por diseño solo solicitud/respuesta.

Burlarse de un servicio web, ya sea creando un servidor HTTP simulado o creando un servicio web simulado en código, va a ser aproximadamente la misma cantidad de trabajo. Si tiene rutas de código X para probar, tiene al menos rutas de código X para manejar en su mock.

Para la opción 2, para simular el servicio web, no se estaría comunicando con el servicio web, sino que estaría utilizando el objeto simulado que tiene respuestas conocidas. [MyCoolWebService performLogin:username withPassword:password]

Se convertiría, en su prueba

[MyMockWebService performLogin:username withPassword:password] El punto clave es que MyCoolWebService y MyMockWebService implementan el mismo contrato (en objective-c, esto sería un Protocolo). OCMock tiene mucha documentación para empezar.

Para una prueba de integración, sin embargo, debería estar haciendo pruebas con el servicio web real, como un entorno QA/staging. Lo que en realidad está describiendo suena más a pruebas funcionales que a pruebas de integración.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-08 06:36:51

Sugiero usar Nocilla. Nocilla es una biblioteca para stubbing peticiones HTTP con un simple DSL.

Digamos que quieres devolver un 404 de google.com. Todo lo que tienes que hacer es:

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

Después de eso, cualquier HTTP a google.com devolverá un 404.

Un ejemplo más completo, donde quieres hacer coincidir un POST con un cuerpo y encabezados determinados y devolver una respuesta enlatada:

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}");

Puede hacer coincidir cualquier solicitud y falsificar cualquier respuesta. Consulte el README para obtener más información detalles.

Los beneficios de usar Nocilla sobre otras soluciones son:

  • Es rápido. No hay servidores HTTP que ejecutar. Tus pruebas correrán muy rápido.
  • No hay dependencias locas que administrar. Además de eso, puedes usar CocoaPods.
  • Está bien probado.
  • Gran DSL que hará que su código sea realmente fácil de entender y mantener.

La principal limitación es que solo funciona con frameworks HTTP construidos sobre NSURLConnection, como AFNetworking, MKNetworkKit o NSURLConnection simple.

Espero que esto ayude. Si necesitas algo más, estoy aquí para ayudarte.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-28 03:16:07

Asumo que está utilizando Objective-C. Para Objective-C OCMock es ampliamente utilizado para burlarse/pruebas unitarias (su segunda opción).

Usé OCMock por última vez hace más de un año, pero por lo que recuerdo es un framework de burla completo y puede hacer todas las cosas que se describen a continuación.

Una cosa importante acerca de las burlas es que puedes usar tanto o tan poco de la funcionalidad real de tus objetos. Puede crear un simulacro ' vacío '(que tendrá todos los métodos es su objeto, pero no hará nada) y anular solo los métodos que necesita en su prueba. Esto generalmente se hace cuando se prueban otros objetos que dependen del mock.

O puede crear un simulacro que actuará como se comporta su objeto real, y extraer algunos métodos que no desea probar en ese nivel (por ejemplo, métodos que realmente acceden a la base de datos, requieren conexión de red, etc.).). Esto generalmente se hace cuando se está probando el objeto burlado sí mismo.

Es importante entender que no se crean burlas de una vez por todas. Cada prueba puede crear burlas para los mismos objetos de nuevo basado en lo que se está probando.

Otra cosa importante sobre las burlas es que puedes 'grabar' scenarious (secuencias de llamadas) y tus 'expectativas' sobre ellas (qué métodos detrás de las escenas deben ser llamados, con qué parámetros y en qué orden), luego 'reproducir' el escenario - la prueba fallará si las expectativas no fueron cumplido. Esta es la principal diferencia entre el TDD clásico y el mockist. Tiene sus pros y sus contras (véase el artículo de Martin Fowler).

Consideremos ahora tu ejemplo específico (Usaré pseudo-sintaxis que se parece más a C++ o Java que a Objective C):

Digamos que tiene un objeto de clase LoginForm que representa la información de inicio de sesión introducida. Tiene (entre otros) métodos setName(String),setPassword(String), bool authenticateUser(), y Authenticator* getAuthenticator().

También tiene un objeto de clase Authenticator que tiene (entre otros) métodos bool isRegistered(String user), bool authenticate(String user, String password), y bool isAuthenticated(String user).

Así es como puedes probar algunos escenarios simples:

Create MockLoginForm mock con todos los métodos vacíos excepto los cuatro mencionados anteriormente. Los tres primeros métodos usarán la implementación real LoginForm; getAuthenticator() se eliminará para devolver MockAuthenticator.

Crear MockAuthenticator mock que utilizará alguna base de datos falsa (como una estructura de datos interna o un archivo) para implementar sus tres métodos. La base de datos contendrá solo una tupla: ('rightuser','rightpassword').

Testusno registrado

Escenario de repetición:

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

Expectativas:

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

TestWrongPassword

Escenario de repetición:

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

Expectativas:

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

TestLoginOk

Escenario de repetición:

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

Expectativas:

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

Espero que esto ayude.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-05 20:56:48

Puede hacer un servicio web simulado de manera bastante efectiva con una subclase NSURLProtocol:

Cabecera:

@interface MyMockWebServiceURLProtocol : NSURLProtocol
@end

Aplicación:

@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

La rutina interesante es-startLoading, en la que debe procesar la solicitud y localizar el archivo estático correspondiente a la respuesta en el paquete de aplicaciones antes de redirigir el cliente a esa URL de archivo.

Se instala el protocolo con

[NSURLProtocol registerClass:[MyMockWebServiceURLProtocol class]];

Y referenciarlo con URLs como

mymock://mockhost/mockpath?mockquery

Esto es considerablemente más simple que implementar un servicio web real ya sea en una máquina remota o localmente dentro de la aplicación; la compensación es que simular encabezados de respuesta HTTP es mucho más difícil.

 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-11 12:06:36

OHTTPStubs es un marco bastante grande para hacer lo que quieres que ha ganado mucha tracción. De su github readme:

OHTTPStubs es una biblioteca diseñada para stub sus solicitudes de red muy fácilmente. Puede ayudarte:

  • Pruebe sus aplicaciones con datos de red falsos (stubbed desde el archivo) y simule redes lentas, para verificar el comportamiento de su aplicación en malas condiciones de red
  • Escribir pruebas unitarias que utilizan datos de red falsos de su accesorio.

Funciona con NSURLConnection, el nuevo iOS7/OSX.9's NSURLSession, AFNetworking (ambos 1.x y 2.x), o cualquier marco de red que utilice el Sistema de Carga de URL de Cocoa.

Los encabezados OHHTTPStubs están completamente documentados usando comentarios Appledoc-like / Headerdoc-like en los archivos de encabezado. También puede leer la documentación en línea aquí.

Aquí hay un ejemplo:

[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"}];
}];

Puedes encontrar ejemplos de uso adicionales en la página 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/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-05-22 18:24:36