OCUnit тестирование доставки NSNotification
Для игры, которую я разрабатываю, у меня есть несколько классов моделей, которые вызывают уведомления, когда их состояние изменяется. Затем представление подписывается на эти уведомления и может реагировать на них.
Я делаю свои модульные тесты для модели с помощью 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
или добавлю новый unit test), все начнет работать как ожидалось.
Я предполагаю, что это должно быть связано с проблемой синхронизации или NSNotificationCenter
в некоторой степени полагаться на цикл выполнения.
Есть ли какие-либо рекомендации по его тестированию? Я знаю, что я мог бы добавить сеттер в Board
и ввести собственный NSNotificationCenter
, но я ищу более быстрый способ сделать это (возможно, какой-то трюк о том, как заменить NSNotificationCenter
динамически).
Ответы
Ответ 1
Нашел проблему. При тестировании уведомлений вам нужно удалить наблюдателя после того, как вы его протестировали. Рабочий код:
- (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];
}
Если вы не удалите наблюдателя, после запуска теста и некоторых локальных переменных центр уведомлений попытается уведомить эти старые объекты при запуске любого последующего теста, запускающего одно и то же уведомление.
Ответ 2
Нет проблем с синхронизацией или проблем, связанных с runloop, поскольку все в вашем коде несовместимо и должно выполняться немедленно. NSNotificationCenter только откладывает доставку уведомлений, если вы используете NSNotificationQueue.
Я думаю, что все правильно в отрывке, который вы опубликовали. Может быть, есть проблема с изменчивыми атрибутами массива. Вы начали и сохранили его правильно? Попробуйте добавить некоторый объект вручную вместо использования трюка уведомления.
Ответ 3
Если вы подозреваете, что ваши тесты имеют проблемы с синхронизацией - вы можете подумать о том, чтобы внедрить свой собственный механизм уведомления в свой объект панели (который, вероятно, всего лишь оболочка существующей версии для 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.