Подтвердить что ты не робот

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 динамически).

4b9b3361

Ответ 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.