2009-07-06 13 views
6

Me pregunto cómo hacer esto. Tengo un método que toma un parámetro y, en función de algunas propiedades de ese parámetro, crea otro objeto y opera en él. El código es como la siguiente:¿Cómo puedo probar un objeto interno de un método en Objective-C?

- (void) navigate:(NavContext *)context { 
    Destination * dest = [[Destination alloc] initWithContext:context]; 
    if (context.isValid) { 
    [dest doSomething]; 
    } else { 
    // something else 
    } 
    [dest release]; 
} 

Lo que quiero para verificar si es que context.isValid es cierto, que doSomething se llama el mostrador, pero no sé cómo probar que (o si eso es aún posible) utilizando OCMock o cualquier otro método de prueba tradicional, ya que ese objeto se crea completamente dentro del alcance del método. ¿Voy por esto de la manera incorrecta?

Respuesta

5

Usted podría use OCMock, pero tendría que modificar el código para tomar un objeto Destination o para usar un objeto singleton que podría reemplazar primero con su objeto simulado.

La forma más limpia de hacer esto, probablemente sería implementar un método

-(void) navigate:(NavContext *)context destination:(Destination *)dest; 

. Cambiar la implementación de -(void) navigate:(NavContext *)context a lo siguiente:

- (void) navigate:(NavContext *)context { 
    Destination * dest = [[Destination alloc] initWithContext:context]; 
    [self navigate:context destination:dest]; 
    [dest release]; 
} 

Esto permitirá que sus pruebas para llamar al método con un parámetro adicional directamente. (En otros idiomas, implementaría esto simplemente proporcionando un valor predeterminado para el parámetro de destino, pero Objective-C no admite parámetros predeterminados).

+0

Esta parece ser la respuesta más fácil, pero parece un poco complicado tener el objeto de destino en vivo en múltiples ámbitos cuando en realidad no es necesario en ningún otro lugar, solo para respaldar las pruebas fáciles. Supongo que hay que hacer concesiones en algún lugar :) – Kevlar

+0

Sí; si desea poder sustituir un objeto Destino por separado, entonces debe tener alguna forma de pasarlo. Esta es la manera más limpia que conozco para hacer eso. –

0

Es completamente posible, utilizando técnicas tan interesantes como method swizzling, pero es probable que vaya por el camino equivocado. Si no hay absolutamente ninguna forma de observar los efectos de invocar doSomething desde una prueba unitaria, ¿no es el hecho de que invoca doSomething un detalle de implementación?

(Si se va a hacer esta prueba, una manera de lograr sus objetivos estarían reemplazando el método de DestinationdoSomething con uno que avisa a su unidad de prueba y luego pasa la llamada a doSomething.)

+0

Supongo que no es un gran problema si no pruebo que se llama al método. Todavía soy un poco nuevo en las pruebas TDD/unit, por lo que no conozco todas las mejores prácticas :) – Kevlar

1

Lo que quiero verificar es que si context.isValid es verdadero , que se llama a algo en dest

Creo que puede estar probando algo incorrecto aquí. Puede suponer (espero) que las declaraciones booleanas funcionan correctamente en ObjC. ¿No querrías probar el objeto Contexto en su lugar? Si context.isValid está garantizado que se ejecuta la rama [dest doSomething].

+0

Me estoy burlando del objeto de contexto ya que en esta prueba no me importa cómo se crea, me importa que el método haga lo correcto en función de las propiedades del contexto. – Kevlar

+0

Además, si la implementación de navegación: alguna vez cambia, es posible que desee verificar que se llame a algo. –

+0

Ahh es justo, estaba siendo un poco miope. Me gusta el enfoque de BJ Homer entonces. – EightyEight

0

Me gusta utilizar métodos de fábrica en esta situación.

@interface Destination(Factory) 
+ (Destination *)destinationWithContext:(NavContext *)context; 
@end 

@implementation Destination(Factory) 
+ (Destination *)destinationWithContext:(NavContext *)context 
{ 
    return [[Destination alloc] initWithContext:context]; 
} 
@end 

continuación hago un FakeClass:

#import "Destination+Factory.h" 

@interface FakeDestination : Destination 
+ (id)sharedInstance; 
+ (void)setSharedInstance:(id)sharedInstance; 
// Note! Instance method! 
- (Destination *)destinationWithContext:(NavContext *)context; 
@end 

@implementation FakeDestination 
+ (id)sharedInstance 
{ 
    static id _sharedInstance = nil; 
    if (!_sharedInstance) 
    { 
     _sharedInstance = [[FakeDestination alloc] init]; 
    } 
    return _sharedInstance; 
} 
+ (void)setSharedInstance:(id)sharedInstance 
{ 
    _sharedInstance = sharedInstance; 
} 
// Overrides 
+ (Destination *)destinationWithContext:(NavContext *)context { [FakeDestination.sharedInstance destinationWithContext:context]; } 
// Instance 
- (Destination *)destinationWithContext:(NavContext *)context { return nil; } 
@end 

Una vez que lo configuran, sólo tiene que swizzle the class methods para + (Destination *)destinationWithContext:(NavContext *)context;

Ahora ya está listo para:

id destinationMock = [OCMock mockForClass:FakeDestination.class]; 
// do the swizzle 
[FakeDestination setSharedInstance:destinationMock]; 
[[destinationMock expect] doSomething]; 
// Call your method 
[destinationMock verify]; 

Esta es una buena cantidad de codificación por adelantado, pero es muy reutilizable.

Cuestiones relacionadas