【问题标题】:OCUnit testing NSNotification deliveryOCUnit 测试 NSNotification 传递
【发布时间】:2009-06-09 14:17:45
【问题描述】:

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

我正在使用 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 将通过调用addObject: 方法将通知添加到NSMutableArray

但是,当我运行它时,我看到 addObject: 被发送到其他对象(不是我的 NSMutableArray),导致 OCUnit 停止工作。但是,如果我注释掉一些代码(例如 release 调用,或添加新的单元测试),一切都会按预期开始工作。

我假设这与时间问题有关,或者 NSNotificationCenter 以某种方式依赖于运行循环。

有什么建议可以测试一下吗?我知道我可以在Board 中添加一个设置器并注入我自己的NSNotificationCenter,但我正在寻找一种更快的方法来做到这一点(也许是一些关于如何动态替换NSNotificationCenter 的技巧)。

【问题讨论】:

  • +1 用于单元测试通知的巧妙方式!

标签: iphone objective-c unit-testing


【解决方案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】:

    不存在时间问题或与运行循环相关的问题,因为代码中的所有内容都是非并发的,应立即执行。 NSNotificationCenter 仅在您使用 NSNotificationQueue 时才会推迟通知传递。

    我认为您发布的 sn-p 中的一切都是正确的。可变数组“通知”可能存在问题。您是否正确初始化并保留它?尝试手动添加一些对象,而不是使用通知技巧。

    【讨论】:

    • 我正在使用 [NSMutableArray arrayWithCapacity] 分配数组。我没有保留它(它是一个局部变量,所以 NSAutoReleasePool 不会释放它)。
    • 发现了我的问题。我不会从 NSNotificationCenter 中删除观察者,因此当第二个测试运行时,它会尝试通知堆中不再存在的对象。
    【解决方案3】:

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

    即:

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

    或者,您可以考虑使用委托而不是通知。这里有一组很好的赞成/反对幻灯片:http://www.slideshare.net/360conferences/nsnotificationcenter-vs-appdelegate

    【讨论】:

    • 我认为通知在这种情况下很好,因为我在 View 层上异步触发动画。我试图避免注入我的自定义通知类,以简化测试代码和主类,但到目前为止它似乎是唯一的选择。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多