Swift中async/await结合TestScheduler测试及Async Algorithms测试探讨
TestScheduler与async/await结合及Swift Async Algorithms时间相关运算符测试方案
一、Pointfree Combine Schedulers的TestScheduler可与async/await结合实现虚拟时间推进
完全可以实现,核心是将TestScheduler作为调度器注入到async代码的执行上下文,替代默认的全局调度器,从而手动控制时间线,彻底避免真实等待。
示例代码:
import CombineSchedulers import XCTest final class AsyncAwaitSchedulerTests: XCTestCase { func testDebouncedAsyncTask() async { let testScheduler = TestScheduler() var receivedResult: String? // 用TestScheduler包裹async任务的执行 await testScheduler.run { let delayedTask = Task { try await testScheduler.sleep(for: .seconds(1)) return "completed" } receivedResult = await delayedTask.value } // 未推进时间时,任务未完成 XCTAssertNil(receivedResult) // 手动推进1秒虚拟时间 testScheduler.advance(by: .seconds(1)) // 任务此时已完成,可验证结果 XCTAssertEqual(receivedResult, "completed") } }
原理是TestScheduler接管了所有依赖时间的异步操作的时间源,sleep、定时器等操作都会基于虚拟时间执行,而非真实时间。
二、Swift Async Algorithms中时间相关运算符的测试方案
Swift Async Algorithms的debounce、throttle等运算符支持传入自定义Scheduler参数,结合TestScheduler就能实现虚拟时间测试,无需等待真实时长。
示例代码(测试debounce运算符):
import AsyncAlgorithms import CombineSchedulers import XCTest final class AsyncAlgorithmsTimeTests: XCTestCase { func testDebounceOperator() async { let testScheduler = TestScheduler() let inputStream = AsyncStream<String> { continuation in continuation.yield("initial") continuation.yield("final") continuation.finish() } var collectedOutput: [String] = [] await testScheduler.run { // 将TestScheduler传入debounce的scheduler参数 for await value in inputStream.debounce(for: .seconds(1), scheduler: testScheduler) { collectedOutput.append(value) } } // 未推进时间时,debounce还未触发输出 XCTAssertTrue(collectedOutput.isEmpty) // 推进1秒虚拟时间 testScheduler.advance(by: .seconds(1)) // 此时debounce完成,仅收集到最后一次输入 XCTAssertEqual(collectedOutput, ["final"]) } }
三、通用优化建议:避免真实等待的核心要点
- 所有依赖时间的async函数/运算符,都要设计成可注入自定义调度器的形式,不要硬编码使用全局调度器(比如
DispatchQueue.main或Task.sleep的默认调度器)。 - 示例:将生产代码改为支持调度器注入
// 生产代码:支持传入自定义调度器,默认使用全局调度器 func loadRemoteData(with scheduler: some Scheduler = DispatchQueue.global()) async throws -> Data { try await scheduler.sleep(for: .seconds(3)) return try Data(contentsOf: URL(string: "https://example.com/data")!) } // 测试代码:注入TestScheduler实现虚拟时间测试 func testLoadRemoteData() async throws { let testScheduler = TestScheduler() var loadedData: Data? await testScheduler.run { loadedData = try? await loadRemoteData(with: testScheduler) } XCTAssertNil(loadedData) testScheduler.advance(by: .seconds(3)) XCTAssertNotNil(loadedData) }
内容的提问来源于stack exchange,提问作者esilver
相关产品推荐
相关产品推荐

