Objective C - 单元测试 dispatch_async 块?

Posted

技术标签:

【中文标题】Objective C - 单元测试 dispatch_async 块?【英文标题】:Objective C - Unit testing dispatch_async block? 【发布时间】:2012-09-17 16:45:23 【问题描述】:

我阅读了其他针对该问题提出解决方案的帖子。但是,他们的解决方案需要将 hacky 代码添加到我的应用程序中才能对其进行测试。对我来说,干净的代码比单元测试更重要。

我经常在我的应用中使用 dispatch_async,但我在对其进行单元测试时遇到了麻烦。问题是块在我的测试完成后执行,因为它在主队列上异步运行。有没有办法以某种方式等到块被执行然后继续测试。

我不想仅仅因为单元测试而将完成传递给块

- (viod)viewDidLoad

   [super viewDidLoad];

   // Test passes on this
   [self.serviceClient fetchDataForUserId:self.userId];


   // Test fails on this because it's asynchronous
   dispatch_async(dispatch_get_main_queue(), ^
      [self.serviceClient fetchDataForUserId:self.userId];
   );


- (void)testShouldFetchUserDataUsingCorrectId

   static NSString *userId = @"sdfsdfsdfsdf";
   self.viewController.userId = userId;
   self.viewController.serviceClient = [[OCMockObject niceMockForClass:[ServiceClient class]];

   [[(OCMockObject *)self.viewController.serviceClient expect] fetchDataForUserId:userId];
   [self.viewController view]; 
   [(OCMockObject *)self.viewController.serviceClient verify];

【问题讨论】:

【参考方案1】:

简单地运行主循环,让它调用异步块:

- (void)testShouldFetchUserDataUsingCorrectId 
   static NSString *userId = @"sdfsdfsdfsdf";
   self.viewController.userId = userId;
   self.viewController.serviceClient = [[OCMockObject niceMockForClass:[ServiceClient class]];

   [[(OCMockObject *)self.viewController.serviceClient expect] fetchDataForUserId:userId];
   [self.viewController view];
   [[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
   [(OCMockObject *)self.viewController.serviceClient verify];

我想这可能会在负载较重的系统上失败,或者如果您有一堆其他东西(计时器或其他块)在主线程上运行。如果是这种情况,您需要运行运行循环更长的时间(这会减慢您的测试用例),或者重复运行它直到满足模拟对象的期望或达到超时(这需要向模拟对象添加一个方法以查询其期望是否得到满足)。

【讨论】:

使用此代码,块根本不会被调用,即使在测试完成后也不会。 我已经修改了我的答案。我更彻底地测试了这种方法。 谢谢。这种方法似乎不可靠,但我还没有找到更好的解决方案,所以我会继续使用它,希望它不会破坏我的测试。【参考方案2】:

将执行包装到dispatch_group 中,然后等待该组通过dispatch_group_wait() 完成所有已调度的块的执行。

【讨论】:

非常有趣。以前从未听说过 dispatch_group。我可能不会再使用 NSOperationQueues 了。我对 GCD 的唯一问题是缺少此功能 @aryaxt 是的,调度组非常有用,但很少在任何地方提及(甚至苹果官方文档也不是很详细) 这是非常有用的技术。它应该会得到更多的提升。 这太棒了。谢谢【参考方案3】:

使用类似的方法签名创建一个dispatch_async 包装器,然后调用真正的dispatch_async。依赖项将包装器注入到您的生产类中并使用它。

然后制作一个模拟包装器,它记录排队的块并有一个额外的方法来同步运行所有排队的块。如果正在执行的块依次排队更多块,则可能执行递归“阻塞接收”。

在您的单元测试中,将模拟包装器注入到被测系统中。然后,即使 SUT 认为它正在执行异步工作,您也可以让所有事情同步发生。

dispatch_group 听起来也是一个不错的解决方案,但需要您的生产类“知道”在它排队的块末尾对调度组执行 ping 操作。

【讨论】:

以上是关于Objective C - 单元测试 dispatch_async 块?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Swift 代码的 Objective C 测试类

Objective-C 单元测试是不是需要头文件?

如何从 Swift 单元测试访问测试目标中存在的 Objective-C 代码?

Objective-C 中的单元测试私有类

Objective-C 单元测试资源是不是会增加整体文件大小

使用 Objective-C++ 时单元测试未解析的符号