尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Quick 共享用例(Shared Examples)完全指南:用 Shared Assertions 复用测试模板代码

发布时间:2026/9/24 15:20:12

资讯中心
01
ARTICLE

Quick 共享用例(Shared Examples)完全指南:用 Shared Assertions 复用测试模板代码

Quick 共享用例(Shared Examples)完全指南:用 Shared Assertions 复用测试模板代码
测试开发工具【免费下载链接】QuickThe Swift (and Objective-C) testing framework.项目地址https://gitcode.com/gh_mirrors/qu/Quick点击查看免费下载导读在编写单元测试时同一组断言逻辑往往需要应用于多个不同的被测对象——例如多个遵循同一协议的类型。Quick 框架提供了 shared examples共享用例机制允许你把这些重复的测试模板抽离出来定义一次再在任意多个 Spec 中通过itBehavesLike复用。本文将基于 Quick 仓库中的 Documentation/zh-cn/SharedExamples.md 展开结合源码与测试用例带你掌握共享用例的定义、上下文传参、聚焦/跳过以及类型安全的BehaviorContext替代方案让测试代码真正做到“一次编写处处复用”。什么是共享用例什么时候该用它在某些场合下一组特定的测试代码可以应用在不同的对象上。假设有一个叫Edible的协议当一只海豚吃了标识为Edible的食物时它会变得高兴而Mackerel鲭鱼和Cod鳕鱼都遵循Edible协议。此时“吃了可食用食物会让海豚开心”这一行为既适用于Mackerel也适用于Cod。如果不使用共享用例你需要为MackerelSpec和CodSpec各写一遍几乎完全相同的it断言而使用 Quick 的 shared examples只需定义一次行为模板两个 Spec 各自通过一行itBehavesLike即可复用。基础用法定义并引用一组共享用例下面的例子为某些Edible的食物定义了一组共享用例以测试 mackerel 和 cod 的行为。在 Swift 中共享用例定义在QuickConfiguration子类的configure方法中通过sharedExamples(name) { ... }注册然后在使用处用itBehavesLike(name) { context }展开// Swift import Quick import Nimble class EdibleSharedExamplesConfiguration: QuickConfiguration { override class func configure(_ configuration: Configuration) { sharedExamples(something edible) { (sharedExampleContext: escaping SharedExampleContext) in it(makes dolphins happy) { let dolphin Dolphin(happy: false) let edible sharedExampleContext()[edible] dolphin.eat(edible) expect(dolphin.isHappy).to(beTruthy()) } } } } class MackerelSpec: QuickSpec { override class func spec() { var mackerel: Mackerel! beforeEach { mackerel Mackerel() } itBehavesLike(something edible) { [edible: mackerel] } } } class CodSpec: QuickSpec { override class func spec() { var cod: Cod! beforeEach { cod Cod() } itBehavesLike(something edible) { [edible: cod] } } }同样的能力在 Objective-C 中通过宏QuickConfigurationBegin/QuickSpecBegin和函数sharedExamples/itBehavesLike完成// Objective-C import Quick; import Nimble; QuickConfigurationBegin(EdibleSharedExamplesConfiguration) (void)configure:(Configuration *configuration) { sharedExamples(something edible, ^(QCKDSLSharedExampleContext exampleContext) { it(makes dolphins happy, ^{ Dolphin *dolphin [[Dolphin alloc] init]; dolphin.happy NO; idEdible edible exampleContext()[edible]; [dolphin eat:edible]; expect(dolphin.isHappy).to(beTruthy()) }); }); } QuickConfigurationEnd QuickSpecBegin(MackerelSpec) __block Mackerel *mackerel nil; beforeEach(^{ mackerel [[Mackerel alloc] init]; }); itBehavesLike(something edible, ^{ return { edible: mackerel }; }); QuickSpecEnd QuickSpecBegin(CodSpec) __block Cod *cod nil; beforeEach(^{ cod [[Cod alloc] init]; }); itBehavesLike(something edible, ^{ return { edible: cod }; }); QuickSpecEnd注原中文文档 Objective-C 示例中误将名称写作someting edible英文原版为something edible本文已按英文原版修正。请务必保证sharedExamples注册名与itBehavesLike引用名完全一致否则测试套件会直接崩溃详见下文“源码原理”一节。共享用例的组成自由度共享用例可以包括任意数量的it、context和describe代码块也可以像普通 example group 一样包含beforeEach、afterEach等钩子。当使用它们来测试不同对象的相同行为时可以少写很多不必要的重复代码。仓库中的真实测试 Tests/QuickTests/QuickTests/Fixtures/FunctionalTests_SharedExamplesTests_SharedExamples.swift 就演示了这一点——一组名为a group of three shared examples的用例里连写了三个it而被引用的 Spec 只写一行itBehavesLike执行后 Xcode 中会展开为 3 个独立的测试用例见 Tests/QuickTests/QuickTests/FunctionalTests/SharedExamplesTests.swift 中executionCount 3的断言。无上下文参数的共享用例一般来说使用共享用例进行测试时不需要依赖其他额外的对象。在 Swift 中可以简单地用一个不带参数的sharedExample闭包来使用共享用例——当需要测试某种全局状态时这很有用// Swift import Quick sharedExamples(everything under the sea) { // ... } itBehavesLike(everything under the sea)对应的无参重载定义在 Sources/Quick/DSL/DSL.swiftpublic static func sharedExamples(_ name: String, closure: escaping () - Void) { World.sharedWorld.sharedExamples(name) { _ in closure() } }注意itBehavesLike(name)这一无上下文的引用形式见 DSL.swift内部会自动补一个返回空字典的 context{ return [:] }因此注册端无需感知差异。如果你使用 Objective-C 的话你需要传入一个带QCKDSLSharedExampleContext参数的 block即使你并不打算使用它。这是 Objective-C 方言层面的强制约定QCKDSLSharedExampleContext被定义为NSDictionary * _Nonnull (^)(void)类型的 block见 Sources/QuickObjectiveC/DSL/QCKDSL.h宏sharedExamples期望的QCKDSLSharedExampleBlock也始终接收一个 context 参数。人生有时就是这么的无奈。上下文Context机制如何向共享用例传参上面的例子中itBehavesLike(something edible) { [edible: mackerel] }的尾随闭包就是一个SharedExampleContext。从源码看其类型定义为Sources/Quick/World.swiftpublic typealias SharedExampleContext () - [String: Any] public typealias SharedExampleClosure (escaping SharedExampleContext) - Void也就是说注册端sharedExamples的闭包接收一个SharedExampleContext在it内部调用sharedExampleContext()[edible]取出字典中对应的值引用端itBehavesLike的尾随闭包返回一个[String: Any]字典将具体对象如mackerel、cod以键值对的形式传给共享用例。之所以设计为“闭包返回字典”而不是“直接传字典”是为了延迟求值——context 只有在共享用例真正执行到需要它的那一刻才会被求值从而保证beforeEach中初始化的对象如var mackerel: Mackerel!在求值时已经就绪。仓库测试 SharedExamplesTests.swift 与对应的 FixtureFunctionalTests_SharedExamplesTests_SharedExamples.swift专门验证了“context 中的参数被正确传给共享用例”这一行为。源码原理共享用例的注册、查找与展开共享用例的整个生命周期都可以在 Sources/Quick/World.swift 中找到依据1. 注册World内部维护一个[String: SharedExampleClosure]字典World.swift。registerSharedExampleWorld.swift在写入前会调用raiseIfSharedExampleAlreadyRegistered——同一名称重复注册会触发异常因此共享用例名称在整个测试套件中必须唯一。2. 查找sharedExample(_ name:)World.swift在取用前会调用raiseIfSharedExampleNotRegistered如果找不到对应名称会抛出类似No shared example named xxx has been registered. Registered shared examples: ...的错误并导致测试套件崩溃同时列出所有已注册名称方便排查。3. 展开World.itBehavesLike(_ name:sharedExampleContext:flags:file:line:)Sources/Quick/DSL/WorldDSL.swift的实现揭示了关键细节let callsite Callsite(file: file, line: line) let closure World.sharedWorld.sharedExample(name) let group ExampleGroup(description: name, flags: flags) currentExampleGroup.appendExampleGroup(group) performWithCurrentExampleGroup(group) { closure(sharedExampleContext) } group.walkDownExamples { (example: Example) in example.isSharedExample true example.callsite callsite }即itBehavesLike会以共享用例名创建一个新的ExampleGroup并在该 group 内执行注册时的闭包使共享用例里的it如同被手工写在此处一样执行随后通过walkDownExamples把组内所有 example 标记为isSharedExample true并将 callsite 替换为itBehavesLike调用点的文件与行号——这样 Xcode 中失败用例的定位信息会指向引用处而非定义处便于快速定位是哪个 Spec 触发了共享用例。4. 聚焦与跳过fitBehavesLike与xitBehavesLike本质上是给itBehavesLike传入[Filter.focused: true]/[Filter.pending: true]的 flags见 WorldDSL.swift与fit/xit的过滤语义完全一致。聚焦共享用例fitBehavesLike你也可以使用fitBehavesLike函数来单独测试某个共享用例——当整个测试套件中存在任何聚焦focused的用例时Quick 只会执行被聚焦的部分。三种重载在 Sources/Quick/DSL/DSL.swift 中均有定义// 无上下文版本 fitBehavesLike(everything under the sea) // 带上下文版本 fitBehavesLike(something edible) { [edible: mackerel] } // Behavior 泛型版本 fitBehavesLike(SomethingEdible.self) { mackerel }对应的跳过版本是xitBehavesLikeDSL.swift用于临时禁用某个共享用例组而不删除代码。Objective-C 侧同样提供fitBehavesLike/xitBehavesLike宏见 Sources/QuickObjectiveC/DSL/QCKDSL.h。进阶替代方案类型安全的 BehaviorContextsharedExamples的上下文是无类型的[String: Any]字典键名拼写错误、类型转换失误都要等到运行时才能暴露。Quick 为此提供了基于 Swift 泛型的BehaviorContext方案见英文版文档 Documentation/en-us/SharedExamples.md 与源码 Sources/Quick/Behavior.swiftclass SomethingEdible: BehaviorEdible { override class func spec(_ aContext: escaping () - Edible) { var edible: Edible! beforeEach { edible aContext() } it(makes dolphins happy) { let dolphin Dolphin(happy: false) dolphin.eat(edible) expect(dolphin.isHappy).to(beTruthy()) } } } class MackerelSpec: QuickSpec { override class func spec() { var mackerel: Mackerel! beforeEach { mackerel Mackerel() } itBehavesLike(SomethingEdible.self) { mackerel } } } class CodSpec: QuickSpec { override class func spec() { var cod: Cod! beforeEach { cod Cod() } itBehavesLike(SomethingEdible.self) { cod } } }BehaviorContext的spec(_ aContext:)接收一个返回Context类型实例的闭包编译器会在itBehavesLike(SomethingEdible.self) { mackerel }处校验闭包返回类型必须与Behavior的泛型参数一致从根本上杜绝“字典传错键”这类问题。DSL.swift的文档注释中也明确建议sharedExamples是无类型的优先使用Behavior以获得类型安全DSL.swift。需要注意的是由于依赖 Swift 泛型BehaviorContext无法从 Objective-C 使用ObjC 项目只能使用基于字符串名称的sharedExamples。常见错误与排查结合源码中的保护性检查以下是使用共享用例时最常遇到的三个坑错误场景运行时表现源码依据itBehavesLike引用了未注册的名称抛出No shared example named xxx has been registered测试套件崩溃World.swift两个sharedExamples使用相同名称抛出重复注册异常World.swift在it内部调用itBehavesLike抛出itBehavesLike cannot be used inside it异常WorldDSL.swift并由 SharedExamplesTests.swift 中的raiseException断言覆盖共享用例应像普通describe/context一样被用在 Spec 的顶层结构或嵌套 group 中不能出现在it、beforeEach、afterEach等执行阶段内。小结共享用例通过sharedExamples(名称) { ... }定义一次通过itBehavesLike(名称)在多处引用可包含任意数量的it、context、describe与钩子闭包需要差异数据时使用SharedExampleContext() - [String: Any]以延迟求值的方式向共享用例传参无参数场景可直接使用无闭包版本调试单个共享用例用fitBehavesLike临时禁用用xitBehavesLike追求类型安全时改用BehaviorContext仅限 SwiftObjective-C 方言要求始终传递QCKDSLSharedExampleContextblock。通过将重复断言收敛为共享用例测试套件不仅更简洁也让“同一行为在不同对象上的正确性”获得了一处维护、全局生效的保障。赞分享测试开发工具【免费下载链接】QuickThe Swift (and Objective-C) testing framework.项目地址https://gitcode.com/gh_mirrors/qu/Quick点击查看免费下载相关推荐Quick 共享示例Shared Examples与 Behavior用共享断言消除测试样板代码Quick 共享示例Shared Examples与 Behavior用共享断言消除测试样板代码 在 Swift/Objective C 测试中同一套断测试开发工具猫抓插件浏览器中的资源侦探让网络视频下载变得轻而易举猫抓插件浏览器中的资源侦探让网络视频下载变得轻而易举 还在为无法保存心仪的网络视频而烦恼吗猫抓Cat Catch这款 免费开源 的 浏览器资源嗅探 插测试开发工具AVA Shared Workers 完全指南用共享 Worker 线程扩展 Node.js 测试运行器AVA Shared Workers 完全指南用共享 Worker 线程扩展 Node.js 测试运行器 导读 共享 WorkerShared Worker测试上一篇VobSub字幕转换难题的终极解决方案从格式不兼容到一键完美转换下一篇如何快速掌握OpenTabletDriver跨平台数位板驱动的终极完全教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。