2009-06-09 10 views
12

Para un juego que estoy desarrollando, tengo varias clases de modelo que activan las notificaciones cuando cambia su estado. Luego, la vista se suscribe a esas notificaciones y puede reaccionar ante ellas.OCUnit testing NSNotification delivery

Estoy haciendo mis pruebas unitarias para el modelo con OCUnit, y quiero afirmar que las notificaciones esperadas se publicaron. Por eso, estoy haciendo algo como esto:

- (void)testSomething { 
    [[NSNotificationCenter defaultCenter] addObserver:notifications selector:@selector(addObject:) name:kNotificationMoved object:board]; 

    Board *board = [[Board alloc] init]; 
    Tile *tile = [Tile newTile]; 

    [board addTile:tile]; 

    [board move:tile]; 

    STAssertEquals((NSUInteger)1, [notifications count], nil); 
    // Assert the contents of the userInfo as well here 

    [board release]; 
} 

La idea es que el NSNotificationCenter agregará las notificaciones a la NSMutableArray llamando a su método addObject:.

Cuando lo ejecuto, sin embargo, veo que addObject: se está enviando a algún otro objeto (no a mi NSMutableArray) haciendo que OCUnit deje de funcionar. Sin embargo, si comento algún código (como las llamadas release, o agrego una nueva prueba unitaria) todo comienza a funcionar como se esperaba.

Supongo que esto tiene que ver con un problema de temporización, o NSNotificationCenter dependiendo del ciclo de ejecución de alguna manera.

¿Hay alguna recomendación para probar esto? Sé que podría agregar un colocador en Board e inyectar mi propio NSNotificationCenter, pero estoy buscando una forma más rápida de hacerlo (tal vez algún truco sobre cómo reemplazar el NSNotificationCenter dinámicamente).

+3

+1 para una manera inteligente de probar las notificaciones de la unidad! –

Respuesta

5

Ha encontrado el problema. Al probar las notificaciones, debe eliminar al observador después de haberlo probado. el código de trabajo:

- (void)testSomething { 
    [[NSNotificationCenter defaultCenter] addObserver:notifications selector:@selector(addObject:) name:kNotificationMoved object:board]; 

    Board *board = [[Board alloc] init]; 
    Tile *tile = [Tile newTile]; 

    [board addTile:tile]; 

    [board move:tile]; 

    STAssertEquals((NSUInteger)1, [notifications count], nil); 
    // Assert the contents of the userInfo as well here 

    [board release]; 
    [[NSNotificationCenter defaultCenter] removeObserver:notifications name:kNotificationMoved object:board]; 
} 

Si no consigue eliminar el observador, después de unas pruebas de funcionamiento y algunas variables locales son liberados, el centro de notificación intentará notificar a esos viejos objetos cuando se ejecuta cualquier prueba posterior que desencadena la misma notificación.

0

No hay problemas de sincronización o problemas relacionados con el runloop ya que todo en el código no es concurrente y debe ejecutarse inmediatamente. NSNotificationCenter solo pospone la entrega de notificaciones si usa NSNotificationQueue.

Creo que todo está correcto en el fragmento que ha publicado. Tal vez haya un problema con las 'notificaciones' de la matriz mutable. ¿Iniciaste y retieneste correctamente? Intente agregar algún objeto manualmente en lugar de usar el truco de notificación.

+0

Estoy asignando la matriz con [NSMutableArray arrayWithCapacity]. No lo estoy reteniendo (es una variable local, por lo que NSAutoReleasePool aún no lo lanzará). – pgb

+0

Encontré mi problema. No eliminaré el observador de NSNotificationCenter, por lo tanto, cuando se ejecuta una segunda prueba, intenta notificar a un objeto que ya no existe en el montón. – pgb

0

Si sospecha que sus pruebas tienen problemas de sincronización, puede considerar inyectar su propio mecanismo de notificación en el objeto de la placa (que probablemente sea solo una envoltura de la versión de apple existente).

Eso es:

Board *board = [[Board alloc] initWithNotifier: someOtherNotifierConformingToAProtocol]; 

Es de suponer que sus Entradas del panel de objetos algunos de notificación - que usaría su inyectada notificador en ese código:

-(void) someBoardMethod { 

    // .... 

    // Send your notification indirectly through your object 
    [myNotifier pushUpdateNotification: myAttribute]; 
} 

En su prueba - que ahora tiene un nivel de indirección que puede usar para las pruebas, para que pueda implementar una clase de prueba que se ajuste a su AProtocolo, y tal vez cuente las llamadas pushUpdateNotification:. En tu código real encapsulas el código que probablemente ya tienes en la Junta que hace la notificación.

Por supuesto, esto es un ejemplo clásico de donde MockObjects son útiles - y hay OCMock que bien le permiten hacer esto sin necesidad de tener una clase de prueba para hacer el recuento (ver: http://www.mulle-kybernetik.com/software/OCMock/)

su prueba haría problably tener una línea algo como:

[[myMockNotifer expect] pushUpdateNotification: someAttribute]; 

Alternativamente, usted podría considerar el uso de un delegado en lugar de las notificaciones. Hay un buen conjunto de diapositivas pro/con aquí: http://www.slideshare.net/360conferences/nsnotificationcenter-vs-appdelegate.

+0

Creo que las notificaciones son adecuadas para este caso, ya que desencadenar animaciones en la capa de Vista de forma asincrónica. Estaba intentando evitar inyectar mi clase de notificación personalizada, para simplificar el código de prueba y la clase principal, pero parece ser la única opción hasta ahora. – pgb