用途
本技能介绍一种基于协议的依赖注入(Protocol-based Dependency Injection)模式,目标是让涉及外部依赖的 Swift 代码变得可测试。它通过把文件系统、网络、iCloud 等外部依赖抽象到精简、功能集中的协议(Protocols)后面,使测试可以在不进行实际 I/O 的情况下确定性地运行。
何时激活
- 编写访问文件系统、网络或外部 API 的 Swift 代码时
- 需要在不触发实际故障的情况下测试错误处理路径时
- 构建需要在不同环境(App、测试、SwiftUI 预览)下运行的模块时
- 使用 Swift 并发(Concurrency,如 Actor、Sendable)设计可测试架构时
主要能力与核心模式
1. 定义精简且功能集中的协议
每个协议只处理一个特定的外部关注点。资料给出的示例包括:FileSystemProviding(容器 URL)、FileAccessorProviding(读写与存在性检查)、BookmarkStorageProviding(书签存取)。这些协议均声明为 Sendable。
2. 创建默认(生产环境)实现
例如 DefaultFileSystemProvider 使用 FileManager.default.url(forUbiquityContainerIdentifier:),DefaultFileAccessor 基于 Data(contentsOf:)、data.write(to:options:.atomic) 与 FileManager.default.fileExists(atPath:)。
3. 创建用于测试的模拟实现(Mock)
资料给出 MockFileAccessor:内部维护 files: [URL: Data] 字典,并提供 readError、writeError 可配置错误属性,用于模拟失败路径;标记为 final class ... @unchecked Sendable。另在测试示例中使用了 MockFileSystemProvider(containerURL:)。
4. 使用默认参数注入依赖
示例 SyncManager 为 actor,构造器参数 fileSystem 与 fileAccessor 使用生产环境默认实现,测试时注入 Mock。sync() 在拿不到容器 URL 时抛出 SyncError.containerNotAvailable,否则读取 data.json 并处理数据。
5. 使用 Swift 测试框架编写测试
示例使用 import Testing,配合 @Test、#expect、#expect(throws:) 断言:验证容器缺失时抛出 SyncError.containerNotAvailable、读取数据正确、读取错误时优雅处理等场景。
最佳实践
- 单一职责:每个协议只处理一个关注点,不要创建拥有过多方法的“万能协议(God Protocols)”
- 符合 Sendable:协议在 Actor 边界之间使用时为必须
- 默认参数:生产代码默认使用真实实现,仅在测试中指定模拟对象
- 错误模拟:为模拟对象设计可配置的错误属性,以便测试失败路径
- 仅对边界进行模拟:模拟外部依赖(文件系统、网络、API),不要对内部类型进行模拟
需避免的反模式
- 创建一个涵盖所有外部访问的大而全的协议
- 对没有外部依赖的内部类型进行模拟
- 使用
#if DEBUG条件编译块而不是正确的依赖注入 - 在与 Actor 配合使用时忘记让协议符合
Sendable - 过度工程:如果一个类型没有外部依赖,则不需要为其定义协议
适用场景
- 任何涉及文件系统、网络或外部 API 的 Swift 代码
- 测试在真实环境下难以触发的错误处理路径
- 构建需要同时在 App、测试和 SwiftUI 预览上下文中运行的模块
- 使用 Swift 并发(Actor、结构化并发)并需要可测试架构的应用
限制
资料未提供具体的安装命令、包管理方式或平台版本要求,暂未提供相关信息。