swift-concurrency-extras

swift-concurrency-extras

增强Swift并发代码的可测试性与可靠性工具库

swift-concurrency-extras是一个开源库,为Swift并发编程提供实用工具集。该库增强了Swift并发代码的可测试性和可靠性,包含LockIsolated类型用于安全访问共享状态、Stream和Task相关辅助函数,以及串行执行工具。它主要面向需要编写可靠并发代码和单元测试的开发者,有助于简化异步编程中常见挑战的处理过程。

Swift并发编程测试异步编程开源库Github开源项目

swift-concurrency-extras

CI Slack

可靠且可测试的Swift并发。

了解更多

这个库旨在支持为Point-Free制作的库和剧集,Point-Free是一个由Brandon WilliamsStephen Celis主持的探索Swift编程语言的视频系列。

你可以在这里观看所有剧集。

<a href="https://www.pointfree.co/collections/concurrency/"> <img alt="视频海报图片" src="https://yellow-cdn.veclightyear.com/2b54e442/888106b8-0f78-4ea2-89fe-b7e23f5ed500.jpeg" width="600"> </a>

动机

这个库提供了许多工具,使Swift并发变得更容易使用和更易于测试。

LockIsolated

LockIsolated类型帮助将其他值包装在隔离的上下文中。它使用锁将值包装在一个类中,允许你通过同步接口读写该值。

该库为两种Swift流类型提供了多个辅助API:

  • 有一些辅助函数可以将任何AsyncSequence符合性擦除为任一具体流类型。这允许你将流类型视为某种"类型擦除"的AsyncSequence

    例如,假设你有这样一个依赖客户端:

    struct ScreenshotsClient { var screenshots: () -> AsyncStream<Void> }

    然后你可以构建一个将NotificationCenter.Notifications异步序列"擦除"为流的活跃实现:

    extension ScreenshotsClient { static let live = Self( screenshots: { NotificationCenter.default .notifications(named: UIApplication.userDidTakeScreenshotNotification) .map { _ in } .eraseToStream() // ⬅️ } ) }

    使用eraseToThrowingStream()来传播来自抛出异常的异步序列的失败。

  • Swift 5.9的makeStream(of:)函数已经被向后移植。它在需要覆盖返回流的依赖端点的测试中很有用:

    let screenshots = AsyncStream.makeStream(of: Void.self) let model = FeatureModel(screenshots: { screenshots.stream }) XCTAssertEqual(model.screenshotCount, 0) screenshots.continuation.yield() // 模拟截图 XCTAssertEqual(model.screenshotCount, 1)
  • 提供了静态的AsyncStream.neverAsyncThrowingStream.never辅助函数,它们表示永远存在且从不发出的流。它们在需要用永远挂起且不发出的流覆盖依赖端点的测试中很有用。

    let model = FeatureModel(screenshots: { .never })
  • 提供了静态的AsyncStream.finishedAsyncThrowingStream.finished(throwing:)辅助函数,它们表示立即完成且不发出的流。它们在需要用立即完成/失败的流覆盖依赖端点的测试中很有用。

任务

该库为Task类型增加了新功能。

  • 静态函数Task.never()可以异步返回任何类型的值,但通过永久挂起来实现。这对于以不需要实际从该端点返回数据的方式满足依赖要求很有用。

    例如,假设你有这样一个依赖客户端:

    struct SettingsClient { var fetchSettings: () async throws -> Settings }

    你可以在测试中通过等待Task.never()来覆盖客户端的fetchSettings端点,使其永久挂起:

    SettingsClient( fetchSettings: { try await Task.never() } )
  • Task.cancellableValue是一个属性,它等待非结构化任务的value属性,同时从当前异步上下文传播取消。

  • Task.megaYield()是一个粗暴的工具,可以通过多次挂起当前任务来使不稳定的异步测试变得稳定一些,增加其他异步工作有足够时间启动的机会。在可能的情况下,优先考虑使用串行执行的可靠性。

串行执行

由于运行时处理挂起点的方式,Swift中的一些异步代码notoriously难以测试。该库提供了一个静态函数withMainSerialExecutor,它试图串行和确定性地运行操作中产生的所有任务。这个函数可以用来使异步测试更快、更稳定。

警告:这个API仅用于测试,以使测试更可靠。请不要在应用程序代码中使用它。

我们说它"_尝试_串行和确定性地运行操作中产生的所有任务",因为它在底层依赖Swift运行时中的一个全局可变变量来完成工作,如果这个可变变量在操作期间发生变化,就无法保证其作用域。

例如,考虑以下看似简单的模型,它发起网络请求并在请求进行时管理isLoading状态:

@Observable class NumberFactModel { var fact: String? var isLoading = false var number = 0 // 显式注入请求依赖以使其可测试,也可以通过依赖管理库提供 let getFact: (Int) async throws -> String func getFactButtonTapped() async { self.isLoading = true defer { self.isLoading = false } do { self.fact = try await self.getFact(self.number) } catch { // TODO: 处理错误 } } }

我们希望能够编写一个测试来确认isLoading状态先变为true然后变为false。你可能希望它像这样简单:

func testIsLoading() async { let model = NumberFactModel(getFact: { "\($0) is a good number." }) let task = Task { await model.getFactButtonTapped() } XCTAssertEqual(model.isLoading, true) XCTAssertEqual(model.fact, nil) await task.value XCTAssertEqual(model.isLoading, false) XCTAssertEqual(model.fact, "0 is a good number.") }

然而,这几乎100%会失败。问题在于,创建非结构化Task后的下一行会在非结构化任务内部的行之前执行,所以我们永远不会检测到isLoading状态变为true的时刻。

你可能希望通过使用Task.yield来在getFactButtonTapped方法被调用和请求完成之间插入一些时间:

func testIsLoading() async { let model = NumberFactModel(getFact: { "\($0) is a good number." }) let task = Task { await model.getFactButtonTapped() } + await Task.yield() XCTAssertEqual(model.isLoading, true) XCTAssertEqual(model.fact, nil) await task.value XCTAssertEqual(model.isLoading, false) XCTAssertEqual(model.fact, "0 is a good number.") }

但这仍然在绝大多数情况下失败。

这些问题以及更多问题可以通过在主串行执行器上运行整个测试来解决。你还需要在getFact端点中插入一个小的yield,因为Swift能够内联不实际执行异步工作的异步闭包:

func testIsLoading() async { + await withMainSerialExecutor { let model = NumberFactModel(getFact: { + await Task.yield() return "\($0) is a good number." }) let task = Task { await model.getFactButtonTapped() } await Task.yield() XCTAssertEqual(model.isLoading, true) XCTAssertEqual(model.fact, nil) await task.value XCTAssertEqual(model.isLoading, false) XCTAssertEqual(model.fact, "0 is a good number.") + } }

这个小小的改变使得这个测试100%确定性地通过。

文档

该库的最新文档可在此处获取。

致谢

感谢Pat Brown和Thomas Grapperon在发布前对该库提供反馈。特别感谢Kabir Oberai帮助我们解决了Xcode的一个bug,并使我们能够在库中提供串行执行工具。

其他库

Concurrency Extras只是让Swift中编写可测试代码变得更容易的众多库之一。

  • Case Paths:用于处理和测试枚举的工具。

  • Clocks:一些时钟,使Swift并发更易于测试和更加多功能。

  • Combine Schedulers:一些调度器,使Combine更易于测试和更加多功能。

  • Composable Architecture:一个库,用于以一致且可理解的方式构建应用程序,考虑到了组合、测试和人体工程学。

  • Custom Dump:用于调试、比较和测试应用程序数据结构的工具集合。

  • Dependencies:受SwiftUI的"环境"启发的依赖管理库。

  • Snapshot Testing:通过记录和对比制品来断言你的应用程序。

  • XCTest Dynamic Overlay:从应用程序代码调用XCTFail和其他通常仅用于测试的辅助函数。

许可证

该库根据MIT许可证发布。详情请参见LICENSE

编辑推荐精选

Vora

Vora

免费创建高清无水印Sora视频

Vora是一个免费创建高清无水印Sora视频的AI工具

Refly.AI

Refly.AI

最适合小白的AI自动化工作流平台

无需编码,轻松生成可复用、可变现的AI自动化工作流

酷表ChatExcel

酷表ChatExcel

大模型驱动的Excel数据处理工具

基于大模型交互的表格处理系统,允许用户通过对话方式完成数据整理和可视化分析。系统采用机器学习算法解析用户指令,自动执行排序、公式计算和数据透视等操作,支持多种文件格式导入导出。数据处理响应速度保持在0.8秒以内,支持超过100万行数据的即时分析。

AI工具酷表ChatExcelAI智能客服AI营销产品使用教程
TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
AIWritePaper论文写作

AIWritePaper论文写作

AI论文写作指导平台

AIWritePaper论文写作是一站式AI论文写作辅助工具,简化了选题、文献检索至论文撰写的整个过程。通过简单设定,平台可快速生成高质量论文大纲和全文,配合图表、参考文献等一应俱全,同时提供开题报告和答辩PPT等增值服务,保障数据安全,有效提升写作效率和论文质量。

AI辅助写作AI工具AI论文工具论文写作智能生成大纲数据安全AI助手热门
博思AIPPT

博思AIPPT

AI一键生成PPT,就用博思AIPPT!

博思AIPPT,新一代的AI生成PPT平台,支持智能生成PPT、AI美化PPT、文本&链接生成PPT、导入Word/PDF/Markdown文档生成PPT等,内置海量精美PPT模板,涵盖商务、教育、科技等不同风格,同时针对每个页面提供多种版式,一键自适应切换,完美适配各种办公场景。

AI办公办公工具AI工具博思AIPPTAI生成PPT智能排版海量精品模板AI创作热门
潮际好麦

潮际好麦

AI赋能电商视觉革命,一站式智能商拍平台

潮际好麦深耕服装行业,是国内AI试衣效果最好的软件。使用先进AIGC能力为电商卖家批量提供优质的、低成本的商拍图。合作品牌有Shein、Lazada、安踏、百丽等65个国内外头部品牌,以及国内10万+淘宝、天猫、京东等主流平台的品牌商家,为卖家节省将近85%的出图成本,提升约3倍出图效率,让品牌能够快速上架。

iTerms

iTerms

企业专属的AI法律顾问

iTerms是法大大集团旗下法律子品牌,基于最先进的大语言模型(LLM)、专业的法律知识库和强大的智能体架构,帮助企业扫清合规障碍,筑牢风控防线,成为您企业专属的AI法律顾问。

SimilarWeb流量提升

SimilarWeb流量提升

稳定高效的流量提升解决方案,助力品牌曝光

稳定高效的流量提升解决方案,助力品牌曝光

Sora2视频免费生成

Sora2视频免费生成

最新版Sora2模型免费使用,一键生成无水印视频

最新版Sora2模型免费使用,一键生成无水印视频

下拉加载更多