Objective C - 如何对dispatch_async块进行单元测试?

15

我看到其他帖子提供了解决此问题的方案。然而,它们的解决方案需要将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];
}
3个回答

41

运行主循环以让它短暂地调用异步块:

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

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


使用这段代码后,块根本没有被调用,即使测试已经完成。 - aryaxt
我已经修改了我的答案。我更全面地测试了这种方法。 - rob mayoff
谢谢。这种方法似乎不是特别可靠,但我还没有发现更好的解决方案,所以我会继续使用它,希望它不会破坏我的测试。 - aryaxt

11

将执行过程包装在dispatch_group中,然后通过dispatch_group_wait()等待组完成所有分派的块的执行。


非常有趣。以前从未听说过dispatch_group。我可能不再使用NSOperationQueues了。我在GCD中唯一遇到的问题是缺乏这种功能。 - aryaxt
@aryaxt 是的,调度组非常有用,但很少在任何地方提到(即使官方的苹果文档也没有详细说明)。 - JustSid
这是非常有用的技术。它应该得到更多的赞。 - e2l3n
这太棒了。谢谢。 - Jan

1
创建一个类似方法签名的dispatch_async包装器,然后调用真正的dispatch_async。将包装器注入到您的生产类中并使用它。
接下来制作一个模拟包装器,记录已排队的块,并具有额外的方法以同步运行所有已排队的块。如果依次执行的块排队了更多的块,可以执行递归的“块嵌套”。
在单元测试中,将模拟包装器注入到正在测试的系统中。尽管SUT认为它正在进行异步工作,但您可以使所有操作同步发生。 dispatch_group听起来也是一个不错的解决方案,但需要您的生产类“知道”在排队的块结束时通知调度组。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接