How to test AsyncSequence and AsyncStream 如何测试 AsyncSequence 和 AsyncStream

How to test AsyncSequence and AsyncStream 如何测试 AsyncSequence 和 AsyncStream

温馨提示:本文最后更新于2025-02-10 10:55:20,某些文章具有时效性,若有错误或已失效,请在下方留言

Concurrent Swift code often streams values over time rather than returning them all at once, so it’s important to be able to write tests to check an AsyncStreamAsyncSequence, or similar sends back a certain number of values. We can do this in both Swift Testing and XCTest, using confirmations and expectations respectively.
并发 Swift 代码通常会随着时间的推移流式传输值,而不是一次返回所有值,因此能够编写测试来检查 AsyncStreamAsyncSequence 或类似代码会发回一定数量的值非常重要。我们可以在 Swift Testing 和 XCTest 中分别使用 confirmations 和 expectations 来做到这一点。

As an example, let’s say we were trying to test the following AsyncSequence, which doubles numbers until the value wraps around, at which point it stops:
例如,假设我们尝试测试以下 AsyncSequence,它将数字加倍,直到值换行,此时它停止:

struct DoubleGenerator: AsyncSequence, AsyncIteratorProtocol {
    var current = 1

    mutating func next() async -> Int? {
        defer { current &*= 2 }

        if current < 0 {
            return nil
        } else {
            return current
        }
    }

    func makeAsyncIterator() -> DoubleGenerator {
        self
    }
}

Tip: &*= multiples with overflow, meaning that rather than running out of room when the value goes beyond the highest number of a 64-bit integer, it will instead flip around to be negative. We use this to our advantage, returning nil when we reach that point.
提示:&*= overflow 的倍数,这意味着当值超过 64 位整数的最大数字时,它不会耗尽空间,而是会翻转为负数。我们利用这一点,当我们到达该点时返回 nil

To test that with Swift Testing, we need to use confirmation(expectedCount:) to say how many doubles we expect to be sent back to us. When we use confirmation(), we’ll be given a testing confirmation function that we call to say “yes, the work completed successfully,” which in the case of our generator means it sent a value back.
要使用 Swift Testing 进行测试,我们需要使用 confirmation(expectedCount:) 来表示我们期望发送回给我们的 doubles 数量。当我们使用 confirmation() 时,我们将得到一个测试确认函数,我们调用该函数说 “yes, the work completed successfully”,对于我们的生成器来说,这意味着它发送了一个值。

The “expected count” part refers to how often we call that testing conformation function. So, here we know that our generator can double a 64-bit integer 63 times, so we could say we expect the confirmation to be called 63 times, then loop over the generator and call the confirmation each time a value comes back:
“预期计数”部分是指我们调用该测试构象函数的频率。所以,这里我们知道我们的生成器可以将 64 位整数加倍 63 次,因此我们可以说我们预计确认会被调用 63 次,然后遍历生成器并在每次值返回时调用确认:

@Test("DoubleGenerator should create 63 doubles")
func testDoubling() async {
    let generator = DoubleGenerator()

    await confirmation(expectedCount: 63) { confirm in
        for await _ in generator {
            confirm()
        }
    }
}

There are three important things you need to be aware of with this code:
使用此代码时,您需要注意三个重要事项:

  1. We’re saying we expect exactly 63 confirmations to happen. Any other value (62, 64, etc) will cause the test to fail.
    我们说我们预计会有 63 个确认发生。任何其他值(62、64 等)都会导致测试失败。
  2. Your code must have finished executing fully by the time the confirmation() closure finishes – if you attempt to use a completion closure or similar, the test won’t succeed because confirmation() doesn’t know to wait.
    在 confirmation() 闭包完成时,您的代码必须已经完全执行完毕 – 如果您尝试使用完成闭包或类似方法,则测试不会成功,因为 confirmation() 不知道等待。
  3. The default value for expectedCount is 1, but you can also use 0 to mean “the confirmation function should never be called.”
    expectedCount 的默认值为 1,但您也可以使用 0 来表示“永远不应调用确认函数”。

To get the same result with XCTest we need to use XCTestExpectation with two specific properties set: expectedFulfillmentCount to say that we expect 63 fulfillments of the test (equivalent to Swift Testing’s confirmations), and assertForOverFulfill to say that fulfilling 64 or more times is unwanted – that we expect exactly 63 fulfillments.
要使用 XCTest 获得相同的结果,我们需要使用设置了两个特定属性的 XCTestExpectation:expectedFulfillmentCount 表示我们预计测试的 63 次执行(相当于 Swift Testing 的确认),assertForOverFulfill 表示不需要执行 64 次或更多次 – 我们期望恰好有 63 次执行。

Here’s that in code:  代码中的内容如下:

final class AsyncSequenceTests: XCTestCase {
    func test_doubleGeneratorContainsCorrectValues() async {
        let generator = DoubleGenerator()
        let expectation = XCTestExpectation(description: "DoubleGenerator should create 63 doubles")
        expectation.expectedFulfillmentCount = 63
        expectation.assertForOverFulfill = true

        for await _ in generator {
            expectation.fulfill()
        }

        await fulfillment(of: [expectation], timeout: 1)
    }
}

Notice the very short timeout here – even 1 second is a long time to double an integer 63 times, but it’s better than XCTest’s default of no limit on time.
请注意这里的超时时间非常短 —— 即使是 1 秒也是一个整数加倍 63 倍的漫长时间,但它比 XCTest 默认的无时间限制要好。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享