Project Icon

swift-concurrency-extras

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

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

swift-concurrency-extras

CI Slack

可靠且可测试的Swift并发。

了解更多

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

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

视频海报图片

动机

这个库提供了许多工具,使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

项目侧边栏1项目侧边栏2
推荐项目
Project Cover

豆包MarsCode

豆包 MarsCode 是一款革命性的编程助手,通过AI技术提供代码补全、单测生成、代码解释和智能问答等功能,支持100+编程语言,与主流编辑器无缝集成,显著提升开发效率和代码质量。

Project Cover

AI写歌

Suno AI是一个革命性的AI音乐创作平台,能在短短30秒内帮助用户创作出一首完整的歌曲。无论是寻找创作灵感还是需要快速制作音乐,Suno AI都是音乐爱好者和专业人士的理想选择。

Project Cover

有言AI

有言平台提供一站式AIGC视频创作解决方案,通过智能技术简化视频制作流程。无论是企业宣传还是个人分享,有言都能帮助用户快速、轻松地制作出专业级别的视频内容。

Project Cover

Kimi

Kimi AI助手提供多语言对话支持,能够阅读和理解用户上传的文件内容,解析网页信息,并结合搜索结果为用户提供详尽的答案。无论是日常咨询还是专业问题,Kimi都能以友好、专业的方式提供帮助。

Project Cover

阿里绘蛙

绘蛙是阿里巴巴集团推出的革命性AI电商营销平台。利用尖端人工智能技术,为商家提供一键生成商品图和营销文案的服务,显著提升内容创作效率和营销效果。适用于淘宝、天猫等电商平台,让商品第一时间被种草。

Project Cover

吐司

探索Tensor.Art平台的独特AI模型,免费访问各种图像生成与AI训练工具,从Stable Diffusion等基础模型开始,轻松实现创新图像生成。体验前沿的AI技术,推动个人和企业的创新发展。

Project Cover

SubCat字幕猫

SubCat字幕猫APP是一款创新的视频播放器,它将改变您观看视频的方式!SubCat结合了先进的人工智能技术,为您提供即时视频字幕翻译,无论是本地视频还是网络流媒体,让您轻松享受各种语言的内容。

Project Cover

美间AI

美间AI创意设计平台,利用前沿AI技术,为设计师和营销人员提供一站式设计解决方案。从智能海报到3D效果图,再到文案生成,美间让创意设计更简单、更高效。

Project Cover

AIWritePaper论文写作

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

投诉举报邮箱: service@vectorlightyear.com
@2024 懂AI·鲁ICP备2024100362号-6·鲁公网安备37021002001498号