首页/新闻资讯/正文详情

Sourcery AutoMockable 模板实战:为 Swift 协议自动生成测试 Mock

发布时间:2026/9/26 7:46:32 来源:云帆数科 栏目:资讯中心
Sourcery AutoMockable 模板实战:为 Swift 协议自动生成测试 Mock
代码生成开发工具【免费下载链接】SourceryMeta-programming for Swift, stop writing boilerplate code.项目地址https://gitcode.com/gh_mirrors/so/Sourcery点击查看免费下载Sourcery 是 Swift 的元编程Meta-programming工具其中AutoMockable模板是其最受欢迎的玩法之一只要给协议打上一个标记就能自动生成对应的ProtocolNameMock测试桩类替你把“实现协议 记录调用 控制返回值”这套最枯燥的样板代码全部写掉。本文以 guides/Mocks.md 为主线结合 Templates/Templates/AutoMockable.stencil 模板源码与 Templates/Tests 下的测试用例讲清标记方式、生成规则、进阶能力与已知限制读完你可以在自己的测试工程里直接落地这套 Mock 生成方案。一、为什么需要自动生成协议 Mock在单元测试中我们经常需要替依赖的协议造一个“替身”既能记录这个协议被调用过几次、传入了什么参数又能按测试需要返回预设结果。手写这样的 Mock 类极其机械——每个函数都要写called标记、参数记录、返回值存放每加一个协议成员就要同步维护一遍还容易漏写。Sourcery 的AutoMockable模板正是为此设计的。它扫描源代码中的协议凡是实现了AutoMockable协议或带有AutoMockable注解的协议都会自动生成一个名为ProtocolNameMock的类类中为每个函数和属性生成对应的记录与桩逻辑。原文档 guides/Mocks.md 将其定位为能覆盖你 90% 的 Mock 需求帮你删掉最无聊的样板代码。二、两种标记方式继承协议或使用注解方式一让目标协议继承AutoMockable在测试源码中定义空协议AutoMockable凡是继承它的协议都会被模板识别见 Templates/Tests/Context/AutoMockable.swift 中的定义public protocol AutoMockable {} protocol BasicProtocol: AutoMockable { func loadConfiguration() - String? func save(configuration: String) }方式二使用sourcery: AutoMockable注解不想让被测协议“沾染”测试协议的继承关系时可以用注解标记无需改动协议声明本身见 Templates/Tests/Context/AutoMockable.swift/// sourcery: AutoMockable protocol AnnotatedProtocol { func sayHelloWith(name: String) }两种方式的底层判定在模板源码 Templates/Templates/AutoMockable.stencil 中完全等价{% for type in types.protocols where type.based.AutoMockable or type|annotated:AutoMockable %}即要么协议based于AutoMockable继承链上出现它要么被annotated注解。注解的具体书写规则单行、行尾、sourcery:begin/end区块等可参考 Writing templates 一文模板也支持type|annotated:AutoMockable这类 Stencil 过滤器。三、生成的 Mock 结构总览对每个命中条件的协议模板生成class ProtocolNameMock: ProtocolName类内按协议声明顺序输出对每个函数原样实现该函数生成名称CallsCount计数变量与Called布尔计算属性用于断言“函数是否被调用过”生成名称ReceivedArguments元组多个参数时或名称Received参数单个参数时记录最后一次调用传入的参数生成名称ReceivedInvocations数组记录历次调用的参数调用历史对有返回值的函数生成名称ReturnValue变量未设置 Closure 时函数直接返回它生成名称Closure可选闭包设置后函数会调用它并返回其结果——这是实现“按测试定制行为”的钩子。对每个属性生成同名同类型的可读写gettable and settable变量具体形式取决于属性类型可选型、数组/字典、普通非可选型各有不同见下文。以文档 guides/Mocks.md 中的示例输出为参照一个带回调的函数会生成class MockableServiceMock: MockableService { //MARK: - functionWithArguments var functionWithArgumentsCalled false var functionWithArgumentsReceivedArguments: (firstArgument: String, onComplete: (String)- Void)? //MARK: - functionWithCallback var functionWithCallbackCalled false var functionWithCallbackReceivedArguments: (firstArgument: String, onComplete: (String)- Void)? func functionWithCallback(_ firstArgument: String, onComplete: escaping (String)- Void) { functionWithCallbackCalled true functionWithCallbackReceivedArguments (firstArgument: firstArgument, onComplete: onComplete) } ... }需要注意实际模板的命名规则比文档示例更精细。为了区分重载与同名方法模板通过cleanString宏AutoMockable.stencil#L19把方法名中的( ) : - .等字符替换为下划线再用swiftifyMethodName宏AutoMockable.stencil#L20-L24拼接“方法名 返回类型名”所以真实生成的变量名形如loadConfigurationStringCallsCount、saveConfigurationStringVoidReceivedConfiguration可在 Templates/Tests/Expected/AutoMockable.expected 中直接查看。四、函数 Mock 的生成细节与源码依据函数生成逻辑集中在mockMethod宏Templates/Templates/AutoMockable.stencil#L71-L139。对照 Templates/Tests/Expected/AutoMockable.expected 中BasicProtocolMock的真实输出可以把规则拆成几块1. 调用计数与“是否被调用”var loadConfigurationStringCallsCount 0 var loadConfigurationStringCalled: Bool { return loadConfigurationStringCallsCount 0 }Called是计算属性等价于“计数大于 0”函数体第一行执行CallsCount 1。模板中对单参数函数与多参数函数的记录形式不同单参数生成Received参数名如ReceivedConfiguration: (String)?与对应的ReceivedInvocations数组多参数生成ReceivedArguments具名元组与ReceivedInvocations数组如save的(configuration: String)?。2. 返回值ReturnValue与Closure双通道对有返回值的方法模板生成ReturnValue变量并在方法体内实现“二选一”逻辑AutoMockable.stencil#L128-L133if let loadConfigurationStringClosure loadConfigurationStringClosure { return loadConfigurationStringClosure() } else { return loadConfigurationStringReturnValue }也就是说默认返回ReturnValue未赋值时非可选类型为隐式解包可选初值即崩溃——这是有意设计强迫你在测试中先设置返回值或 Closure一旦设置了Closure则转而调用 Closure。Closure类型与函数签名完全一致例如var loadConfigurationStringClosure: (() - String?)?。这给了测试两种用法// 用法一直接设置返回值 mock.loadConfigurationStringReturnValue cached // 用法二用 Closure 模拟更复杂的行为 mock.loadConfigurationStringClosure { computed-\(Int.random(in: 0...9)) }3. throws 方法与类型化错误模板对throws方法额外生成ThrowableError变量AutoMockable.stencil#L30-L32方法体先抛错再走返回逻辑见 AutoMockable.expected#L1953-L1970var doOrThrowStringThrowableError: (any Error)? var doOrThrowStringCallsCount 0 ... func doOrThrow() throws - String { doOrThrowStringCallsCount 1 if let error doOrThrowStringThrowableError { throw error } if let doOrThrowStringClosure doOrThrowStringClosure { return try doOrThrowStringClosure() } else { return doOrThrowStringReturnValue } }对于 Swift 的类型化抛出throws(CustomError)、throws(any Error)、throws(Never)模板同样生成对应的ThrowableError: (CustomError)?与Closure: (() throws(CustomError) - String)?见 TypedThrowableProtocolMock。但泛型类型化抛出generic typed throws目前未完整支持模板会生成fatalError(Generic typed throws are not fully supported yet)占位。4. async 方法模板完整支持async/async throws方法Closure携带async标记调用处使用await/try await见 AsyncProtocolMock。available等函数属性也会被原样搬到生成的实现上。5. 初始化器协议中的init会被生成为required init同样记录参数并调用对应的Closure见 InitializationProtocolMockrequired init(intParameter: Int, stringParameter: String, optionalParameter: String?) { init...ReceivedArguments (intParameter: intParameter, stringParameter: stringParameter, optionalParameter: optionalParameter) init...ReceivedInvocations.append((intParameter: intParameter, ...)) init...Closure?(intParameter, stringParameter, optionalParameter) }6. 静态方法附带static func reset()当协议含静态方法时模板额外生成static func reset()将计数、接收参数、Closure、ThrowableError 全部复位AutoMockable.stencil#L149-L169方便测试之间清理状态见 StaticMethodProtocolMock。五、属性 Mock 的生成细节属性按类型分三种策略对应模板 AutoMockable.stencil#L171-L186见 VariablesProtocolMock可选型属性——直接生成同名可选变量var company: String?数组/字典属性——生成默认空容器var kids: [String] [] var universityMarks: [String: Int] [:]普通非可选属性——生成计算属性 underlying名称隐式解包可选存储var name: String { get { return underlyingName } set(value) { underlyingName value } } var underlyingName: (String)!底层采用underlying隐式解包可选的用意与返回值的!一致提醒测试人员先给属性赋值再读取。async / throws 属性同样被支持get async、get throws、get async throws模板为其生成CallsCount/Called/Closure/ThrowableError与underlying存储见 AsyncThrowingVariablesProtocolMock。六、更进阶的生成能力关联类型associatedtype与泛型约束模板会把协议的关联类型提取为 Mock 类的泛型参数并把where约束原样搬过去见 TestProtocolMock协议关联类型若指向具体类型则生成typealias。Sendable若协议继承Sendable生成的 Mock 类会追加, unchecked Sendable以满足并发检查AutoMockable.stencil#L392-L394示例见 SendableProtocolMock。访问级别public协议生成的 Mock 类、属性和方法均带public并自动生成public init() {}见 AccessLevelProtocolMock。扩展中的默认实现模板使用!definedInExtension过滤跳过仅在扩展中定义的成员带默认实现的扩展声明无需再 mock协议本体同时声明的成员仍会生成。保留字与多行声明方法名为保留字如continue或参数跨多行声明时模板均可正确生成ReservedWordsProtocolMock。七、在项目中运行 AutoMockable命令行方式Sourcery 是命令行工具guides/Usage.md可以手动运行也可以放进 Xcode 的 custom build phase$ ./sourcery --sources sources path --templates templates path --output output path把--templates指向包含 AutoMockable.stencil 的目录即可。模板默认只import Foundation如果你的协议依赖了其他模块可以用--args传入autoMockableImports与autoMockableTestableImports模板在 AutoMockable.stencil#L11-L17 读取它们分别生成普通import与testable import$ ./sourcery --sources Sources --templates Templates/AutoMockable.stencil \ --output Generated --args autoMockableTestableImportsMyApp配置文件方式更推荐在.sourcery.yml中配置部分功能如 exclude 仅配置文件可用并搭配--watch实现模板与源码变更后的自动重新生成sources: - Sources templates: - Templates/AutoMockable.stencil output: Generated args: autoMockableTestableImports: MyApp生成结果默认写入AutoMockable.generated.swift模板名 .generated.swift。若你的模板目录还包含AutoMockable以外的模板可用配置文件的exclude排除不想生成的部分模板。八、模板如何被验证仓库测试体系仓库用一套“输入-期望输出”对比机制持续验证该模板Templates/Tests/Context/AutoMockable.swift 是精心构造的输入——覆盖了基本方法、隐式解包返回值、初始化器、只读/读写属性、重载、保留字、throws/typed throws、async、闭包参数escaping/non-escaping/可空/多参数、any/some存在类型、关联类型、Sendable、下标等 40 余种场景Templates/Tests/Expected/AutoMockable.expected 是对应的完整期望输出2187 行TemplatesTests.swift 在测试启动时用真实sourcery二进制生成文件再与期望输出逐行 diff。因此你在 Templates/Tests/Expected/AutoMockable.expected 里看到的每一种写法都是模板在当前版本下可复现的真实产物。九、已知限制与注意事项原文档 guides/Mocks.md 明确列出以下限制结合模板源码可以进一步确认重载方法会产生编译错误重载函数上方生成的记录变量名称相同如SameShortMethodNamesProtocol中两个start会生成同名记录变量模板虽然会按参数与返回类型区分命名以尽量规避但同名重载场景仍需留意。Workaround删掉其中一个函数顶部多余的记录变量或手动重命名。回调的成败分支需要自己处理对带 completion 回调的方法自动生成只能记录并转发回调无法推断“成功时调用回调 A、失败时调用回调 B”的业务语义——这部分必须利用Closure钩子自己写。它不是手写 Mock 的完整替代品正如原文档所说它“能带你走完 90% 的路”对于超出“改改返回值”的复杂逻辑状态机、调用顺序编排、副作用模拟仍需手写或叠加 Closure 定制。下标subscript未完整支持模板的mockSubscript宏AutoMockable.stencil#L141-L147会生成完整签名的下标但 getter/setter 体内是fatalError(Subscripts are not fully supported yet)见 SubscriptProtocolMock。泛型类型化抛出未完整支持initE ... throws(E)、func doOrRethrowsE ... throws(E)等会生成fatalError占位TypedThrowableProtocolMock需要自行补写实现。命名噪音为区分重载而拼接的“方法名 参数 返回类型”长命名会让生成的变量名偏长这是模板为正确性付出的代价属预期行为。总而言之AutoMockable是“约定优于配置”的典型协议继承或注解声明意图 → Sourcery 扫描 → 模板生成可读的 Mock 源码全程无运行时魔法生成的代码就在你的工程里可以随时检查、修改与断言。把这条流水线接入测试 target 的 build phase 或.sourcery.ymlwatch 模式后协议更新时 Mock 也会同步更新测试维护成本会显著下降。赞分享代码生成开发工具【免费下载链接】SourceryMeta-programming for Swift, stop writing boilerplate code.项目地址https://gitcode.com/gh_mirrors/so/Sourcery点击查看免费下载相关推荐使用 Claudian 时哪些数据会离开本机API 请求与 Collab LAN 流量说明使用 Claudian 时哪些数据会离开本机API 请求与 Collab LAN 流量说明 Claudian 是一个把 Claude Code、Codex、G代码生成开发工具用 Sourcery AutoHashable 模板自动生成 Swift Hashable 实现协议标记驱动告别手写样板代码用 Sourcery AutoHashable 模板自动生成 Swift Hashable 实现协议标记驱动告别手写样板代码 本文以 Sourcery 仓库代码生成开发工具Sourcery 元编程实战指南用模板自动生成 Swift 样板代码Sourcery 元编程实战指南用模板自动生成 Swift 样板代码 Sourcery 是一款面向 Swift 的元编程代码生成器它扫描你的源码、应用你编写代码生成开发工具上一篇WebSocket库Wasm编译浏览器端WebSocket开发终极指南下一篇SMUDebugTool硬件调试利器助力AMD平台性能优化创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关推荐

Superpowers开发工具链:本地化AI编程环境搭建与实战
Superpowers开发工具链:本地化AI编程环境搭建与实战

1. “Superpowers”不是超能力,而是开发者工具链的隐喻性命名“Superpowers”这个词最近在开发者社区里高频出现,但它既不是漫威电影里的变种人设定,也不是某个新出的AI模型代号。它本质上是一套面向现代AI编程工作流的工具集成范式——准确地… · 2026/9/26 7:46:32

TensorFlow ckpt 转 caffemodel 踩坑记:padding 不一致导致精度掉点的排查与配置骨架
TensorFlow ckpt 转 caffemodel 踩坑记:padding 不一致导致精度掉点的排查与配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 7:46:26

DeskcommCRM实施复盘:从选型到落地的完整指南
DeskcommCRM实施复盘:从选型到落地的完整指南

DeskcommCRM 这个名字,圈外听起来可能陌生,但做企业服务销售管理的同行应该不陌生——它是我最近一年反复验证下来,最能扛住中小销售团队日常打磨的客户管理系统之一。上个月项目刚完成验收,趁着今天不忙,我把从选型、… · 2026/9/26 7:46:20

Java+原生双端+小程序全栈零售系统工程实践
Java+原生双端+小程序全栈零售系统工程实践

简介:这是一套面向Java开发者与移动应用全栈工程师的成人健康电商零售系统源码,聚焦两性健康产品线上销售场景,提供安卓、iOS双端原生APP及微信小程序三位一体解决方案,适用于快速搭建合规化私域零售平台或二次开发学习。资源包共… · 2026/9/26 8:21:25

大促“历史最低价”是真是假?用价格曲线拆穿折扣套路
大促“历史最低价”是真是假?用价格曲线拆穿折扣套路

十一月刚过完,各大平台就开始放大促战报:"新史低"三个字刷得满天飞,什么"低至2折"、"全年最低"、"错过再等一年"轮番打在首页上。我盯着后台跳出来的价格提醒看了半天,又翻了翻近半年的历… · 2026/9/26 8:21:19

Claude Code模板化实战:CLAUDE.md与提示词模板搭建指南
Claude Code模板化实战:CLAUDE.md与提示词模板搭建指南

开头:别再逼AI猜你的项目意图了如果你最近用过 Claude Code,大概率会有同感:它在终端里干活麻利是真麻利,但偶尔也会跑偏——你以为它知道项目结构,它其实在按“一般情况”瞎猜;你以为它记得之前定的规范&a… · 2026/9/26 8:21:19

Ternary Bonsai 27B:三值量化+树状稀疏注意力的本地大模型新范式
Ternary Bonsai 27B:三值量化+树状稀疏注意力的本地大模型新范式

1. 为什么是Ternary Bonsai 27B?——不是又一个“小而美”模型,而是三值量化与结构精简的双重突破Ternary Bonsai 27B 这个名字里,“Ternary”和“Bonsai”两个词就直接点破了它的核心设计哲学。它不是在现有大模型基础上简单剪枝或蒸馏出来的… · 2026/9/26 8:21:07

如何自己动手改配列板:用Keychron-Keyboards-Hardware-Design的DXF Plate文件完成第一次Mod实战
如何自己动手改配列板:用Keychron-Keyboards-Hardware-Design的DXF Plate文件完成第一次Mod实战

如何自己动手改配列板:用Keychron-Keyboards-Hardware-Design的DXF Plate文件完成第一次Mod实战 【免费下载链接】Keychron-Keyboards-Hardware-Design Industrial design files for Keychron keyboards and mice. 100 models with CAD assets in STEP, DXF, DWG, a… · 2026/9/26 8:21:07

OpCore-Simplify:导出一份硬件报告,就能生成 OpenCore EFI
OpCore-Simplify:导出一份硬件报告,就能生成 OpenCore EFI

OpCore-Simplify:导出一份硬件报告,就能生成 OpenCore EFI 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify 在 PC 上装 macOS&a… · 2026/9/26 8:21:01

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码