2009-06-09 3 views
12

Для игры, которую я разрабатываю, у меня есть несколько классов моделей, которые вызывают уведомления при изменении их состояния. Затем, вид подписывается на эти уведомления и может реагировать на них.OCUnit testing NSNotification delivery

Я выполняю свои модульные тесты для модели с помощью OCUnit и хочу утверждать, что ожидаемые уведомления были опубликованы. Для этого, я делаю что-то вроде этого:

- (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 добавит уведомления в NSMutableArray путем вызова метода addObject:.

Однако, когда я запускаю его, я вижу, что addObject: отправляется на другой объект (не мой NSMutableArray), в результате чего OCUnit перестает работать. Однако, если я прокомментирую какой-то код (например, звонки release или добавьте новый модульный тест), все начнет работать как ожидалось.

Я предполагаю, что это связано с проблемой синхронизации, или NSNotificationCenter, полагаясь на цикл запуска каким-то образом.

Есть ли рекомендация проверить это? Я знаю, что могу добавить сеттера в Board и ввести свой собственный NSNotificationCenter, но я ищу более быстрый способ сделать это (может быть, какой-то трюк о том, как динамически заменить NSNotificationCenter).

+3

+1 для умного способа уведомления об модульном тестировании! –

ответ

5

Обнаружили проблему. При тестировании уведомлений вам нужно удалить наблюдателя после того, как вы его протестировали. Рабочий код:

- (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]; 
} 

Если вы не в состоянии удалить наблюдателя, после тестовых прогонов и некоторых локальных переменных высвобождаются, центр уведомлений будет пытаться уведомить эти старые объекты при выполнении каких-либо последующих тестов, который вызывает такое же уведомление.

0

Нет проблем с синхронизацией или проблем, связанных с runloop, поскольку все в вашем коде несовместимо и должно выполняться немедленно. NSNotificationCenter только откладывает доставку уведомлений, если вы используете NSNotificationQueue.

Я думаю, что все правильно в отрывке, который вы опубликовали. Возможно, есть проблема с уведомлениями об изменчивом массиве. Вы начали и сохранили его правильно? Попробуйте добавить некоторый объект вручную вместо использования трюка уведомления.

+0

Я выделяю массив с помощью [NSMutableArray arrayWithCapacity]. Я не сохраняю его (это локальная переменная, поэтому NSAutoReleasePool еще не выпустит ее). – pgb

+0

Нашел мою проблему. Я не удаляю наблюдателя из NSNotificationCenter, поэтому, когда выполняется второй тест, он пытается уведомить объект, который больше не существует в куче. – pgb

0

Если вы подозреваете, что ваши тесты имеют проблемы с синхронизацией - вы можете подумать о том, чтобы ввести свой собственный механизм уведомления в свой объект панели (который, вероятно, является только оболочкой существующей версии для Apple).

То есть:

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

Предположительно ваши доски сообщений объектные некоторые уведомления - вы будете использовать ваш впрыскивается Уведомитель в этом коде:

-(void) someBoardMethod { 

    // .... 

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

В тесте - теперь у вас есть уровень косвенности которые вы можете использовать для тестирования, поэтому вы можете реализовать класс тестирования, соответствующий вашему AProtocol, и, возможно, подсчитывает вызовы pushUpdateNotification:. В вашем реальном коде вы инкапсулируете код, который у вас, вероятно, уже есть в Board, который делает уведомление.

Это, конечно, является классическим примером того, где MockObjects полезны - и есть OCMock, которые хорошо позволяют сделать это без того, чтобы иметь тестовый класс, чтобы сделать подсчет (см: http://www.mulle-kybernetik.com/software/OCMock/)

ваш тест будет вероятно, имеет строку примерно так:

[[myMockNotifer expect] pushUpdateNotification: someAttribute]; 

В качестве альтернативы вы можете рассмотреть возможность использования делегата вместо уведомлений. Здесь есть хороший pro/con набор слайдов: http://www.slideshare.net/360conferences/nsnotificationcenter-vs-appdelegate.

+0

Я думаю, что уведомления подходят для этого случая, поскольку я запускаю анимацию на слое просмотра асинхронно. Я пытался не вводить свой собственный класс уведомлений, чтобы упростить код тестирования и основной класс, но пока это единственный выбор. – pgb

Смежные вопросы