如何在 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")
}
我需要为以下方法编写单元测试
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")
}