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

How to test method that dispatch work asynchronously in Swift

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

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

你好,我认为使用 XCTestExpectation 可能会受益,因为这个 api 旨在测试异步操作(您可以阅读更多相关信息 here

就两者之间的比较而言,我唯一能想到的是 XCTestExpectation 您将能够测试服务器超时(假设您的 api 在预计时间。URLSession 的默认时间为 60 秒),带有特定的错误代码和消息。

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

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

如果你写一个测试用例来调用setLabelText(msg:),然后立即检查视图控制器的标签,这是行不通的。

如果您要分派到后台线程,那么我们需要测试来等待线程完成。但这段代码并非如此。

您的生产代码从后台调用 setLabelText(msg:)。但是在主线程上测试代码运行s。因为它已经在主线程上了,所以我们需要做的就是再执行一次 运行 循环。您可以使用我在第 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")
}