如何在Swift中测试异步调度的方法

问题描述

我需要为以下方法编写单元测试

func setLabelText(msg: String) {
     dispatchQueue.main.async {
          self.label.text = msg
      }
  }

解决方法

让我们假设您的测试设置已经创建了一个视图控制器,并调用loadViewIfNeeded()连接任何插座。 (这是来自第5章,“加载视图控制器”。)并且该视图控制器位于一个属性中,我将其命名为sut(表示被测系统)。

如果您编写了一个测试用例以调用setLabelText(msg:),然后立即检查视图控制器的标签,则将无法使用。

如果您已调度到后台线程,那么我们需要测试以等待线程完成。但这不是这种代码。

您的生产代码从后台调用setLabelText(msg:)。但是测试代码在主线程上运行。因为它已经在主线程上,所以我们要做的就是再执行一次运行循环。您可以使用帮助功能来表达这一点,我将在第10章“测试屏幕之间的导航:”中介绍

func executeRunLoop() {
    RunLoop.current.run(until: Date())
}

有了这个,这是一个有效的测试:

func test_setLabelText_thenExecutingMainRunLoop_shouldUpdateLabel() throws {
    sut.setLabelText(msg: "TEST")
    executeRunLoop()
    
    XCTAssertEqual(sut.label.text,"TEST")
}

这成功地测试了方法,并且很快完成。但是,如果另一个程序员出现并更改setLabelText(msg:),将self.label.text = msg调用拉到DispatchQueue.main.async之外怎么办?我在第13章“测试网络响应(和关闭)”中的“在关闭中保留异步代码”一节中描述了此问题。基本上,我们要测试在未运行分派的闭包时标签没有更改。我们可以通过第二项测试来做到这一点:

func test_setLabelText_withoutExecutingMainRunLoop_shouldNotUpdateLabel() throws {
    sut.label.text = "123"
    
    sut.setLabelText(msg: "TEST")
    
    XCTAssertEqual(sut.label.text,"123")
}
,

在我看来,使用XCTestExpectation的Hello可能会受益,因为该API旨在测试异步操作(您可以了解更多关于here的信息)

就两者之间的比较而言,我唯一想到的就是使用XCTestExpectation可以测试服务器超时(假设您的api在预期的时间内没有响应。URLSession具有一个默认时间超过60秒),并带有特定的错误代码和消息。

,

您可以将完成闭包添加到displayMessage并在测试中调用Expectation.fulfill()。另一种完全不同的方法是实现某种演示设计模式,例如协调器或演示者。在这种情况下,您的所有UI演示都将抽象为非异步方法。