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

3 回答 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];
}

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

于 2012-09-17T17:35:24.433 回答
10

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

于 2012-09-17T16:47:49.220 回答
1

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

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

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

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

于 2017-10-27T07:39:33.537 回答