2011-05-22 17 views
6

Estoy trabajando en una aplicación que mostrará UIAlertView al presionar su botón de salida, solo si se ha realizado un progreso en el juego. Me preguntaba cómo usaría OCUnit para interceptar el UIAlertView e interactuar con él, o incluso detectar si se ha presentado. Lo único que puedo pensar es en monkeypatch [UIAlertViewDelegate willPresentAlertView], pero eso me da ganas de llorar.Usando OCUnit para probar si se presenta un UIAlertView

¿Alguien sabe de un mejor método para hacer esto?

Respuesta

4

actualización: Ver mi blog http://qualitycoding.org/testing-alerts/

El problema con mi otra respuesta es que el método -showAlertWithMessage: sí mismo nunca es ejercido por las pruebas unitarias. "Usar pruebas manuales para verificarlo una vez" no es tan malo para escenarios fáciles, pero el manejo de errores a menudo implica situaciones inusuales que son difíciles de reproducir. ... Además, tuve la sensación de que me había quedado corto, y que podría haber una manera más completa. Ahi esta.

En la clase bajo prueba, no cree una instancia UIAlertView directamente. En su lugar, defina un método

+ (Class)alertViewClass 
{ 
    return [UIAlertView class]; 
} 

que se puede reemplazar utilizando "subclase y anulación". (Alternativamente, utilizar la inyección de dependencia y pasar esta clase como un argumento inicializador.)

invocar este para determinar la clase para crear una instancia para mostrar una alerta:

Class alertViewClass = [[self class] alertViewClass]; 
id alert = [[alertViewClass alloc] initWithTitle:...etc... 

Ahora definir una clase de vista de alertas mock.Su función es recordar sus argumentos de inicializador y publicar una notificación, haciéndose pasar como objeto:

- (void)show 
{ 
    [[NSNotificationCenter defaultCenter] postNotificationName:MockAlertViewShowNotification 
                 object:self 
                 userInfo:nil]; 
} 

Su subclase de pruebas (TestingFoo) redefine +alertViewClass para sustituir la maqueta:

+ (Class)alertViewClass 
{ 
    return [MockAlertView class]; 
} 

Realizar la prueba clase registrarse para la notificación El método invocado ahora puede verificar los argumentos pasados ​​al inicializador de alertas y la cantidad de veces que se envió un mensaje a -show.

punta adicional: Además de la alerta mock, I define una clase de verificador de alerta que:

  • Registros para la notificación
  • me deja valores de ajuste esperado
  • Tras la notificación, verifica la estado contra los valores esperados

Así que todas mis pruebas de alerta ahora es crear el verificador, establecer las expectativas, y ejercita la llamada.

+2

Creo que la clase posar sería más útil aquí. Tengo una sensación desagradable si el marco de prueba de mi unidad se interpone en mi código real.Podría escribir un UIAlertViewPoser que establece un indicador cuando se muestra una alerta. http://www.cocoadev.com/index.pl?ClassPosing – wjl

+1

@wjlafrance Interesante, y sería genial si funcionara. Desafortunadamente, poseAsClass: se ha desaprobado en la Mac y nunca fue compatible con iOS. –

+0

Sí, lo noté poco después de publicar. Bueno ... :( – wjl

3

Nota: Por favor vea mi otra respuesta. Lo recomiendo sobre este.

En la clase real, defina un método corto para mostrar una alerta, algo como:

- (void)showAlertWithMessage:(NSString message *)message 
{ 
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil 
                message:message 
                delegate:self 
              cancelButtonTitle:@"OK" 
              otherButtonTitles:nil]; 
    [alert show]; 
    [alert release]; 
} 

Para su prueba, no probar este método real. En su lugar, use "subclase y anulación" para definir un espía que simplemente registra sus llamadas y argumentos. Digamos que la clase original se llama "Foo". Aquí hay una subclase para propósitos de prueba:

@interface TestingFoo : Foo 
@property(nonatomic, assign) NSUInteger countShowAlert; 
@property(nonatomic, retain) NSString *lastShowAlertMessage; 
@end 

@implementation TestingFoo 
@synthesize countShowAlert; 
@synthesize lastShowAlertMessage; 

- (void)dealloc 
{ 
    [lastShowAlertMessage release]; 
    [super dealloc]; 
} 

- (void)showAlertWithMessage:(NSString message *)message 
{ 
    ++countShowAlert; 
    [self setLastShowAlertMessage:message]; 
} 

@end 

Ahora bien, mientras

  • el código llama -showAlertWithMessage: en lugar de mostrar una alerta directamente, y
  • su código de prueba instancia TestingFoo en lugar de Foo,

puede verificar el número de llamadas para mostrar una alerta y el último mensaje.

Dado que esto no ejerce el código real que muestra una alerta, utilice la prueba manual para verificarlo una vez.

+0

hace que la subclase y el paradigma de anulación funcionen si su clase Obj C ya es una subclase del marco de prueba, p. SenTestCase? ¿La única alternativa sería la inyección de dependencia? –

+1

No veo por qué no debería ser capaz de subclasificar cualquier clase concreta que usted defina, para anular un método específico. –

+0

ah, creo que lo tengo. Tiene sentido gracias. –

0

Puede obtener pruebas unitarias para vistas de alertas de forma bastante fluida intercambiando la implementación 'show' de UIAlertView. Por ejemplo, esta interfaz le da una cierta cantidad de habilidades de prueba:

@interface UIAlertView (Testing) 

+ (void)skipNext; 
+ (BOOL)didSkip; 

@end 

con esta aplicación

#import <objc/runtime.h> 
@implementation UIAlertView (Testing) 

static BOOL skip = NO; 

+ (id)alloc 
{ 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
     Method showMethod = class_getInstanceMethod(self, @selector(show)); 
     Method show_Method = class_getInstanceMethod(self, @selector(show_)); 
     method_exchangeImplementations(showMethod, show_Method); 
    }); 
    return [super alloc]; 
} 

+ (void)skipNext 
{ 
    skip = YES; 
} 

+ (BOOL)didSkip 
{ 
    return !skip; 
} 

- (void)show_ 
{ 
    NSLog(@"UIAlertView :: would appear here (%@) [ title = %@; message = %@ ]", skip ? @"predicted" : @"unexpected", [self title], [self message]); 
    if (skip) { 
     skip = NO; 
     return; 
    } 
} 

@end 

Puede escribir pruebas unitarias, por ejemplo, de esta manera:

[UIAlertView skipNext]; 
// do something that you expect will give an alert 
STAssertTrue([UIAlertView didSkip], @"Alert view did not appear as expected"); 

Si desea automatizar pulsando un botón específico en la vista alerta, usted necesitará un poco más de magia. La interfaz recibe dos nuevos métodos de clase:

@interface UIAlertView (Testing) 

+ (void)skipNext; 
+ (BOOL)didSkip; 

+ (void)tapNext:(NSString *)buttonTitle; 
+ (BOOL)didTap; 

@end 

que van como esto

static NSString *next = nil; 

+ (void)tapNext:(NSString *)buttonTitle 
{ 
    [next release]; 
    next = [buttonTitle retain]; 
} 

+ (BOOL)didTap 
{ 
    BOOL result = !next; 
    [next release]; 
    next = nil; 
    return result; 
} 

y el método espectáculo se convierte en

- (void)show_ 
{ 
    if (next) { 
     NSLog(@"UIAlertView :: simulating alert for tapping %@", next); 
     for (NSInteger i = 0; i < [self numberOfButtons]; i++) 
      if ([next isEqualToString:[self buttonTitleAtIndex:i]]) { 
       [next release]; 
       next = nil; 
       [self alertView:self clickedButtonAtIndex:i]; 
       return; 
      } 
     return; 
    } 
    NSLog(@"UIAlertView :: would appear here (%@) [ title = %@; message = %@ ]", skip ? @"predicted" : @"unexpected", [self title], [self message]); 
    if (skip) { 
     skip = NO; 
     return; 
    } 
} 

Esto se puede comprobar de manera similar, pero en lugar de skipNext que' d decir qué botón tocar. P.ej.

[UIAlertView tapNext:@"Download"]; 
// do stuff that triggers an alert view with a "Download" button among others 
STAssertTrue([UIAlertView didTap], @"Download was never tappable or never tapped"); 
4

La última versión de OCMock (2.2.1 al momento de escribir esto) tiene características que lo hacen fácil. Aquí hay algunos ejemplos de código de prueba que anula el método de clase "alloc" de UIAlertView para devolver un objeto simulado en lugar de un UIAlertView real.

id mockAlertView = [OCMockObject mockForClass:[UIAlertView class]]; 
[[[mockAlertView stub] andReturn:mockAlertView] alloc]; 
(void)[[[mockAlertView expect] andReturn:mockAlertView] 
      initWithTitle:OCMOCK_ANY 
       message:OCMOCK_ANY 
       delegate:OCMOCK_ANY 
     cancelButtonTitle:OCMOCK_ANY 
     otherButtonTitles:OCMOCK_ANY, nil]; 
[[mockAlertView expect] show]; 

[myViewController doSomething]; 

[mockAlertView verify]; 
+1

Gracias. Hombre, recuerdo haberlo probado antes de aprender sobre burlas. Eran épocas oscuras – wjl

+0

¿Puedo usar el mismo enfoque para el compositor de correo? Consulte mi pregunta: http://stackoverflow.com/questions/21968556/ocmock-unexpected-method-invooked-though-expected –

+0

¡Esto funciona genial! – vincentjames501