OCUnit测试NSNotification交付

时间:2009-06-09 14:17:45

标签: iphone objective-c unit-testing

对于我正在开发的游戏,我有几个模型类在状态发生变化时触发通知。然后,视图订阅这些通知并对它们做出反应。

我正在使用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添加一个setter并注入我自己的NSNotificationCenter,但我正在寻找一种更快的方法(也许是关于如何动态替换NSNotificationCenter的一些技巧)。

3 个答案:

答案 0 :(得分: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];
}

如果您未能删除观察者,则在测试运行并释放一些局部变量后,通知中心将尝试在运行任何触发相同通知的后续测试时通知这些旧对象。

答案 1 :(得分:0)

没有时间问题或与runloop相关的问题,因为代码中的所有内容都是非并发的,应该立即执行。如果您使用NSNotificationQueue,NSNotificationCenter仅推迟通知传递。

我认为您发布的代码段中的所有内容都是正确的。也许可变数组'通知'存在问题。你是否正确启动并保留了它?尝试手动添加一些对象,而不是使用通知技巧。

答案 2 :(得分:0)

如果您怀疑测试有时间问题 - 您可能需要考虑将自己的通知机制注入您的电路板对象(这可能只是现有苹果版本的包装)。

那是:

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