尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Quick 共享示例(Shared Examples)与 Behavior:用共享断言消除测试样板代码
Quick 共享示例Shared Examples与 Behavior用共享断言消除测试样板代码【免费下载链接】QuickThe Swift (and Objective-C) testing framework.项目地址: https://gitcode.com/gh_mirrors/qu/Quick在 Swift/Objective-C 测试中同一套断言常常需要针对多个不同对象重复执行。Quick 为此提供了两套机制——基于字符串命名的sharedExamples共享示例以及基于泛型的BehaviorContext——让你把公共断言定义一次、处处复用。本文以海豚吃掉可食用对象会变开心为例完整讲解这两种写法的 Swift 与 Objective-C 用法并结合 Quick 源码World、DSL、测试用例说明其注册、展开与执行原理读完后你可以直接在自己的 Spec 中落地共享测试。典型场景同一断言适用于多个对象假设业务代码中存在一个协议Edible可食用当海豚Dolphin吃掉一个Edible对象后海豚会变得开心。Mackerel鲭鱼和Cod鳕鱼都遵循Edibleprotocol Edible { } class Dolphin { private(set) var isHappy: Bool init(happy: Bool) { self.isHappy happy } /// Makes Dolphin happy func eat(_ edible: Edible) { isHappy true } } class Mackerel: Edible { } class Cod: Edible { }现在要验证海豚无论吃鲭鱼还是鳕鱼都会开心。如果为Mackerel和Cod各写一份完整 Spec断言逻辑会完全重复。Quick 的目标就是让这种重复降到最低把吃下某物后开心这一组断言抽出来分别喂给不同的被测对象。方式一sharedExamplesitBehavesLike按名字共享最经典的共享示例 API 由两个函数组成sharedExamples(名字) { context in ... }定义一组可复用的示例闭包内可以包含任意数量的it、context、describe以及beforeEach/afterEachitBehavesLike(名字) { ... }在某个 Spec 的当前位置展开这组共享示例并可通过闭包传入上下文参数。在 Swift 中共享示例定义在QuickConfiguration的子类里以便在任何示例运行前完成注册对应源码见 Sources/Quick/Configuration/QuickConfiguration.swift 中configure(_:)的说明。Swift 完整示例// 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] } } }关键点在于数据流动方式itBehavesLike(something edible) { [edible: mackerel] }的闭包返回一个[String: Any]字典共享示例内部通过sharedExampleContext()[edible]取出对应的被测对象。SharedExampleContext的类型定义位于 Sources/Quick/World.swift/// A closure that, when evaluated, returns a dictionary of key-value /// pairs that can be accessed from within a group of shared examples. public typealias SharedExampleContext () - [String: Any] /// A closure that is used to define a group of shared examples. public typealias SharedExampleClosure (escaping SharedExampleContext) - Void也就是说上下文是一个求值后返回字典的闭包这保证了即使在beforeEach之后才真正求值拿到的也是当次运行的最新对象例如上面beforeEach中新构造的mackerel。Objective-C 完整示例Objective-C 侧使用QuickConfigurationBegin/QuickConfigurationEnd宏声明配置类QuickSpecBegin/QuickSpecEnd宏声明 Spec 类共享示例与itBehavesLike的调用形式与 Swift 对应宏定义见 Sources/QuickObjectiveC/DSL/QCKDSL.h// 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说明日文原版文档中 CodSpec 的声明写成了__block Mackerel *cod属于类型笔误本文按英文版语义修正为Cod *cod同时英文版文档使用的共享示例名为something edible日文版中误写为someting edible实际使用时两者必须完全一致见下文名字唯一性。方式二BehaviorContext类型安全的共享示例sharedExamples以字符串为键、以[String: Any]字典传参灵活但缺乏类型检查。Quick 提供了基于 Swift 泛型的BehaviorContext把上下文类型化编译期就能发现类型错误。其定义在 Sources/Quick/Behavior.swiftopen class BehaviorContext { open class var name: String { return String(describing: self) } open class func spec(_ aContext: escaping () - Context) {} }用法是把公共断言写进Behavior子类的spec(_:)静态方法再在 Spec 里用itBehavesLike(BehaviorType.self) { 上下文 }展开class 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 } } }这里BehaviorEdible的泛型参数是Edible闭包{ mackerel }返回的就是Edible类型由编译器保证。由于BehaviorContext使用了 Swift 泛型它无法从 Objective-C 使用——Objective-C 只能走sharedExamples字符串方案。源码视角共享示例如何注册、查找与展开理解底层机制有助于排查找不到共享示例名字重复等常见问题。三个关键环节都集中在 Sources/Quick/World.swift 与 Sources/Quick/DSL/WorldDSL.swift。1. 注册名字必须全局唯一顶层sharedExamples(name) { ... }最终调用World.sharedWorld.sharedExamples(name, closure:)进入registerSharedExampleSources/Quick/World.swiftinternal func registerSharedExample(_ name: String, closure: escaping SharedExampleClosure) { raiseIfSharedExampleAlreadyRegistered(name) sharedExamples[name] closure }所有共享示例集中存放在World的private var sharedExamples: [String: SharedExampleClosure] [:]字典中。如果重复注册同名示例会触发raiseIfSharedExampleAlreadyRegistered抛出异常如果itBehavesLike引用了未注册的名字raiseIfSharedExampleNotRegistered会抛出异常并列出所有已注册的名字private func raiseIfSharedExampleNotRegistered(_ name: String) { if sharedExamples[name] nil { raiseError(No shared example named \(name) has been registered. Registered shared examples: \(Array(sharedExamples.keys))) } }因此定义名字与引用名字必须逐字符一致这是最常见的出错点。2. 展开把共享示例内联进当前示例组itBehavesLike的底层实现在 Sources/Quick/DSL/WorldDSL.swift。它按名字取出已注册的闭包以共享示例名新建一个ExampleGroup挂到当前示例组下然后在新组中执行闭包nonobjc internal func itBehavesLike(_ name: String, sharedExampleContext: escaping SharedExampleContext, flags: FilterFlags [:], file: FileString, line: UInt) { guard currentExampleMetadata nil else { raiseError(itBehavesLike cannot be used inside \(currentPhase), itBehavesLike may only be used inside context or describe.) } 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 } }这段代码揭示了三个实现事实共享示例本质上是就地展开执行时如同把it等语句手动写进当前位置展开后组内每个Example会被标记为isSharedExample true并把callsite指向调用itBehavesLike的位置这样失败定位会指向调用处而非共享示例定义处itBehavesLike只能出现在context或describe内部若在it块内调用会抛出itBehavesLike cannot be used inside it, itBehavesLike may only be used inside context or describe.异常——这一点被测试 Tests/QuickTests/QuickTests/FunctionalTests/SharedExamplesTests.swift 显式验证。BehaviorContext的展开路径与之对称WorldDSL.swift 中itBehavesLike(_ behavior:context:...)直接用behavior.spec作为闭包、behavior.name作为组名同样标记isSharedExample并重写callsite。3. 配置时机共享示例在示例运行前完成注册QuickConfiguration子类的configure(_:)会在 Quick 运行任何示例之前被统一调用见 Sources/Quick/Configuration/QuickConfiguration.swift 的_configureSubclassesIfNeeded遍历所有QuickConfiguration子类依次执行configure随后world.finalizeConfiguration()冻结配置。因此在 configure 中注册共享示例可以保证后续所有 Spec 执行itBehavesLike时都能查到对应名字。测试固件 Tests/QuickTests/QuickTests/Fixtures/FunctionalTests_SharedExamplesTests_SharedExamples.swift 就是这样一个典型的跨文件共享示例注册点。共享示例可以包含什么it、context、describe 与 Hook共享示例闭包表现得就像一个用describe/context定义的示例组Sources/Quick/DSL/DSL.swift 中sharedExamples的文档注释因此可以包含任意数量的it示例嵌套的context/describe示例组beforeEach/afterEach/justBeforeEach/aroundEach等 hook对应闭包类型见 Sources/Quick/Hooks/Closures.swift。仓库测试 Tests/QuickTests/QuickTests/FunctionalTests/SharedExamplesBeforeEachTests.swift 直接验证了 hook 的执行语义class FunctionalTests_SharedExamples_BeforeEachTests_SharedExamples: QuickConfiguration { override class func configure(_ configuration: QCKConfiguration) { sharedExamples(a group of three shared examples with a beforeEach) { beforeEach { sharedExamplesBeforeEachExecutedCount 1 } it(passes once) {} it(passes twice) {} it(passes three times) {} } } } class FunctionalTests_SharedExamples_BeforeEachSpec: QuickSpec { override class func spec() { beforeEach { specBeforeEachExecutedCount 1 } it(executes the spec beforeEach once) {} itBehavesLike(a group of three shared examples with a beforeEach) } }测试断言Spec 外层的beforeEach总共执行 4 次1 个普通示例 3 个共享示例而共享示例内部的beforeEach恰好执行 3 次每个共享示例一次。这印证了共享示例按调用点展开、内部 hook 随每个示例独立执行的语义。无上下文的共享示例测试全局状态有些场景不需要任何上下文参数。此时 Swift 可以直接使用不带参数的sharedExamples闭包这对测试某种全局状态很有用// Swift import Quick sharedExamples(everything under the sea) { // ... } itBehavesLike(everything under the sea)itBehavesLike有一个不接收 context 的重载它在内部自动补一个返回空字典的闭包public static func itBehavesLike(_ name: String, file: FileString #file, line: UInt #line) { itBehavesLike(name, file: file, line: line, sharedExampleContext: { return [:] }) }Objective-C 注意事项即使在 Objective-C 中不打算使用QCKDSLSharedExampleContext也必须传入一个接收该参数的 blocksharedExamples(name, ^(QCKDSLSharedExampleContext exampleContext) { ... })与itBehavesLike(name, ^NSDictionary *{ return {}; })这是 Objective-C 的硬性要求没有捷径。聚焦与跳过fitBehavesLike与xitBehavesLike与fit/xit对应Quick 为共享示例提供了聚焦与跳过变体定义见 Sources/Quick/DSL/DSL.swiftfitBehavesLike(...)在itBehavesLike前加f当测试套件中存在任何被聚焦的示例时只运行被聚焦的部分共享示例也随之只跑聚焦的那份xitBehavesLike(...)把共享示例标记为 pending不执行其内部代码。三者都有按名字 上下文与按Behavior类型两套重载例如fitBehavesLike(something edible) { [edible: mackerel] } fitBehavesLike(SomethingEdible.self) { mackerel }底层实现上fitBehavesLike只是给itBehavesLike附加[Filter.focused: true]标志、xitBehavesLike附加[Filter.pending: true]标志见 Sources/Quick/DSL/WorldDSL.swift随后由World.includedExamples()的过滤逻辑决定哪些示例真正执行。Quick 自身的聚焦测试 Tests/QuickTests/QuickFocusedTests/FocusedTests.swift 中同时使用了字符串与Behavior两种fitBehavesLike形式Objective-C 侧见 Tests/QuickTests/QuickFocusedTests/FocusedTestsObjC.m。两种方式如何选择维度sharedExamplesitBehavesLikeBehaviorContext语言支持Swift 与 Objective-C仅 Swift依赖泛型上下文类型[String: Any]字典无类型检查泛型Context编译期类型安全命名方式字符串名字全局唯一类名可覆写name定义位置QuickConfiguration.configure任何可访问的Behavior子类适用建议跨语言复用、简单共享场景追求类型安全、共享逻辑较复杂从源码注释看Quick 官方在 DSL.swift 中明确标注sharedExamples是无类型的sharedExamplesis untyped. Please useBehaviorinstead, as it offers type-safety.推荐新代码优先使用Behavior而涉及 Objective-C 或需要运行时动态命名的场景则继续使用字符串方案。常见陷阱小结名字不一致itBehavesLike引用的名字必须与sharedExamples注册的名字逐字符一致否则运行前抛出异常名字重复注册同一个共享示例名只能注册一次重复注册会崩溃调用位置错误itBehavesLike只能在context/describe内使用放进it块会抛出异常测试用例见 SharedExamplesTests.swift 与 BehaviorTests.swiftObjective-C 的 block 签名即使不用上下文也必须提供接收QCKDSLSharedExampleContext的 block配置时机确保注册共享示例的QuickConfiguration子类能被测试 target 发现跨文件共享示例通常集中放在一个配置文件中仓库的 Fixtures/FunctionalTests_SharedExamplesTests_SharedExamples.swift 即是这种组织方式。结语共享断言是 Quick 减少测试样板代码的核心手段sharedExamples提供跨语言的字符串命名复用BehaviorContext提供 Swift 侧的类型安全复用二者共用同一套注册—查找—展开机制。理解了 World.swift 中的注册表与 WorldDSL.swift 中的展开逻辑你就能准确预估共享示例的执行次数、hook 行为与失败定位方式在真实项目中放心地把公共断言抽成可复用的测试资产。【免费下载链接】QuickThe Swift (and Objective-C) testing framework.项目地址: https://gitcode.com/gh_mirrors/qu/Quick创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED

相关推荐

Humanizer InDate.Nine 全面解析:用 DateOnly 表达 9 天/9 周/9 个月/9 年后的日期

Humanizer InDate.Nine 全面解析:用 DateOnly 表达 9 天/9 周/9 个月/9 年后的日期

开发工具 【免费下载链接】Humanizer Humanizer meets all your .NET needs for manipulating and displaying strings, enums, dates, times, timespans, numbers and quantities 项目地址: https://gitcode.com/gh_mirrors/hu/Humanizer 点击查看 免费下载 导读 …

📅 2026/9/24 17:00:20
django CMS 2.3.4 升级指南:WymEditor 修复、挪威语语言码迁移与多站点 slug 冲突防护详解

django CMS 2.3.4 升级指南:WymEditor 修复、挪威语语言码迁移与多站点 slug 冲突防护详解

CMS后端 【免费下载链接】django-cms The easy-to-use and developer-friendly enterprise CMS powered by Django 项目地址: https://gitcode.com/gh_mirrors/dj/django-cms 点击查看 免费下载 本文基于 django CMS 官方 2.3.4 release notes(docs/up…

📅 2026/9/24 16:55:20
Quick 入门实战:在 Xcode 项目中配置 Swift / Objective-C 单元测试

Quick 入门实战:在 Xcode 项目中配置 Swift / Objective-C 单元测试

测试开发工具 【免费下载链接】Quick The Swift (and Objective-C) testing framework. 项目地址: https://gitcode.com/gh_mirrors/qu/Quick 点击查看 免费下载 本篇指南围绕 Quick 测试框架的使用前置环节——在 Xcode 工程中正确搭建测试 Target 与跨语言测试桥…

📅 2026/9/24 16:55:20
MORE NEWS

更多资讯

📰

Jev 发布 3 天后,开源平替 Laya 来了:快 8 倍,权重全开

Jev 发布 3 天后,开源平替 Laya 来了:快 8 倍,权重全开一个不写一个字、只输出带概率判定的模型新品类,一周之内集齐了闭源版、开源版和两份民间复现。AI 不写字了,专管下判断 9 月 15 日,前 OpenAI 研究员…

📰

【AI大模型进阶】整理你的 AI 工具集:构建属于你自己的 AI 开发百宝箱

【AI大模型进阶】整理你的 AI 工具集:构建属于你自己的 AI 开发百宝箱 这是【AI大模型进阶】系列第一百五十课,也是本系列算力工程、云端开发、模型训练运维板块的收官总结课。在前序149节课程中,我们从零入门,逐一掌握了云端算力租赁、SSH远程开发、Tmux终端保活、GPU状态…

📰

MySQL数据库基础——数据库的约束

文章目录约束非空约束 not null唯一约束 unique默认值 default主键 primary keyprimary key主键的自增类型复合主键外键 foreign keycheck(了解)约束 约束是关系型数据库的一个重要功能,主要作用是通过加上约束的校验来保证数据的完整性&…

📰

【Matlab】运动目标无人机视觉跟随控制实现

【Matlab】运动目标无人机视觉跟随控制实现 一、引言 随着智能无人机技术的快速迭代,无人机自主跟随飞行已经成为智能巡检安防监测野外追踪物资跟随运输等场景的核心功能。传统无人机跟随作业多依靠人工遥控或GPS定点跟随模式,存在明显的技术短板。GPS跟随依赖卫星定位信号…

📰

酚醛涂层配套体系的工况适配逻辑与施工边界

在重防腐涂装领域,由酚醛底漆、环氧酚醛中间漆与面漆构成的复合体系,针对特定腐蚀环境展现出突出的防护稳定性。该配套并非单一固定模式,而是依据服役工况的差异,在面漆选型与前道处理上呈现灵活调整。围绕化学品储罐内壁、高温蒸…

📰

【C语言入门8】函数递归

1递归是什么? 递归是一种解决问题的方法,在C语言中,递归就是函数自己调用自己。 一个最简单的C语言递归代码: 这个代码只是拿来示范的,虽然这个代码能运行,但是代码最终也会陷入死递归,导致栈…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬