使用 XCTestExpectation 对异步函数进行单元测试,但它不会等待我设置的秒数

Posted

技术标签:

【中文标题】使用 XCTestExpectation 对异步函数进行单元测试,但它不会等待我设置的秒数【英文标题】:unit test async function by using XCTestExpectation but it doesn't wait for the seconds I set 【发布时间】:2016-06-15 21:15:09 【问题描述】:

我有一个继承 NSThreadMyService 类:

标题:

@interface MyService : NSThread 
  -(void) startMe;
  -(void) doTask;
  ...

实施:

@implementation MyService
  -(void)startMe 
   [self start];
  
  -(void) doTask 
    [self performSelector:@selector(checkData:) onThread:self withObject:nil waitUntilDone:YES];
  

  -(void) checkData 
    ...
    // NOTE: dataChecked is an instance variable.
    dataChecked = YES;
  
@end

我想对上面的-(void)doTask 进行单元测试并验证-(void)checkData 是否真的被调用了。我使用OCMock library 部分模拟MyService

受this tutorial (use XCTestExpectation)的启发,我尝试了以下方法:

       -(void) testCheckData 
          // partial mock MyService
          id myService = [OCMockObject partialMockForObject:[MyService getInstance]];
          [myService startMe];

          // function to test
          [myService doTask];

          // I setup expectation
          XCTestExpectation *expectation = [self expectationWithDescription:@"data checked"];

         // run assertion asynchronisely
         dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
            XCTAssertTrue([self isDataChecked]);
            // expectation fulfill
            [expectation fulfill];
        );

        // wait for 5 seconds
        [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) 
            if (error) 
                NSLog(@"Timeout Error: %@", error);
            
        ];
       

但是,当我运行测试时,waitForExpectationsWithTimeout:handler: 不起作用,我的意思是它不会等待 5 秒,断言部分在被测函数被调用后立即运行。为什么它不等待 5 秒?

====== 更新 ======

我也试过不使用异步块:

-(void) testCheckData 
        // partial mock MyService
        id myService = [OCMockObject partialMockForObject:[MyService getInstance]];
        [myService startMe];

        // function to test
        [myService doTask];

        // I setup expectation
        XCTestExpectation *expectation = [self expectationWithDescription:@"data checked"];

       // run assertion
       XCTAssertTrue([self isDataChecked]);
       // expectation fulfill
       [expectation fulfill];

       // wait for 5 seconds
       [self waitForExpectationsWithTimeout:5.0 handler:^(NSError *error) 
          if (error) 
              NSLog(@"Timeout Error: %@", error);
          
      ];
    

但我还是遇到同样的问题,没有等待5秒,测试立即返回,为什么?

===== AND =====

如果我们忽略上面的更新并返回查看我的原始代码使用异步块,我可能会感到很痛苦。我认为 waitForExpectations:5 应该等待我不需要使用 @ 987654335@循环,为什么我这么认为是因为tutorial。

如果我们查看该教程,它首先显示了使用while循环的旧风格等待,然后,它变成了不使用任何while循环的期望风格,它所做的是设置期望->开始工作(在其工作的完成块中断言),它还有waitForExpectations:代码,看起来和我的代码完全一样带有异步块。我想了解为什么我的原始代码看起来与教程相同但不起作用。我错过了什么吗?

【问题讨论】:

【参考方案1】:

一旦您开始waitForExpectations,您的 dispatch_async 就会有机会运行。它将断言您的数据已经过检查,然后——无论你的数据是否已经过检查——它都将期望标记为已实现。一旦完成,我们不再需要等待,我们可以完成。

这可能不是您想要做的,但这就是代码所说的。

【讨论】:

但是如何解决我的问题?我需要它在断言前等待 5 秒。 正如我在原始问题中所写,您应该循环直到检查数据。只有这样,您才应该发出期望并从异步块返回。在等待调用之后,您可以断言数据是否被检查。 @Avi,我现在不明白,根据教程:bignerdranch.com/blog/asynchronous-testing-with-xcode-6 XCTestExpectation 不是应该摆脱使用 while 循环测试异步函数的旧式方式吗?但是现在还是需要使用while循环,那么使用XCTestExpectation有什么好处呢? 您对循环中应该检查的内容感到困惑。您发布了从异步块断言的代码。那是错误的做法。首先,您应该执行工作,然后您断言工作已正确完成。 while 循环正在等待工作完成,并且仅以这种方式完成,因为您的异步方法没有通知系统。它需要轮询,因为它是这样写的。这并不能使民意调查好;这是必要的。 @Avi,我也有同样的想法,并且已经尝试删除异步块,但我遇到了同样的问题,无需等待,测试立即返回。请在我的帖子中查看我的更新。【参考方案2】:

好的,解决了。

我应该使用dispatch_after 而不是dispatch_async,因为我的测试函数中没有完成块,而该教程中的示例有一个。所以,我需要改成这样:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^
        XCTAssert(...);
        [expectation fulfill];
);

如果您想使用dispatch_async,请查看@Avi 的评论,在异步块中使用while 循环等待。

【讨论】:

以上是关于使用 XCTestExpectation 对异步函数进行单元测试,但它不会等待我设置的秒数的主要内容,如果未能解决你的问题,请参考以下文章

如何知道 XCTestExpectation 当前的履行计数

异步测试

使用 XCTest 进行异步方法测试

异步等待失败 swift API 请求

XCTAssert/XCTFail 在异步回调中是不是安全?

XCTestExpectation 不会等待给定的时间