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 代码?