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),仅供参考
企业数字化 ERP 产品动态
相关推荐
Jackett 跨平台部署 3 步跑通 BT Tracker 聚合 Jackett 跨平台部署 3 步跑通 BT Tracker 聚合
Jackett 把多个 BT Tracker 的搜索结果统一成 Torznab API,让 Sonarr、Radarr 等客户端免适配直接调用。想在 Windows、macOS、Linux 上部署?这篇教程按场景给最短安装路径,并覆盖启动验证、后… · 2026/9/24 16:59:28
templ ADR 0001 解析:如何在文本行内书写 `@Component()` 组件表达式 开发工具代码生成后端 【免费下载链接】templ A language for writing HTML user interfaces in Go. 项目地址: https://gitcode.com/gh_mirrors/te/templ 点击查看 免费下载 符号在 templ 模板语言中用于调用组件表达式(templ element expression&… · 2026/9/24 16:59:22
刷题笔记: 标准刷题结构:public class Main{
public static void main(String[] args){
}
}输入:import java.util.Scanner;
public class Main{
public class void main(String[] args){
Scanner sc new Scanner(String.in);
}
}格式化输出:printf(&… · 2026/9/24 16:59:22
BA | 一周速学业务与系统框架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/24 17:28:23
skywallking 告警配置案例 告警配置# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to yo… · 2026/9/24 17:28:23
k8s下,Harbor 私有镜像仓库部署 环境信息表格服务器IP角色node‑0110.10.6.27k8s 节点 HAProxyKeepalivednode‑0210.10.6.28k8s 节点 HAProxyKeepalivednode‑0310.10.6.29k8s 节点 HAProxyKeepalivedVIP10.10.6.100Keepalived 虚拟 IP,前端负载均衡入口集群版本:K8s v1.28.15&… · 2026/9/24 17:28:23
【股票交易】第 56 章 基本面与技术面的结合 回到目录 文章目录 56.1 把公司质量、价值与交易条件分别判断 一、先明确每类分析负责什么 二、两类分析都需要允许修正 三、价值判断与价格兑现是两个问题 56.2 从宏观与行业,走到公司盈利和股票价格 一、把宏观判断落实到公司变量 二、盈利改善与股价上涨之间,还有估值这一… · 2026/9/24 17:28:17
零基础学ESP32:舵机控制——让设备精准转动到指定角度! 前面我们学了控制LED亮灭、控制继电器开关、让蜂鸣器唱歌——但这些都只是"开"和"关"两种状态。今天要学一个能精准控制角度的设备:舵机。
航模飞机的机翼控制、小车车轮的转向、家用电器的开关、机器人关节的转动……这些需要"转到某个精… · 2026/9/24 17:28:17
智能时代网安护航:端点安全一体化如何筑牢政企终端安全底座 2026年国家网络安全宣传周以“网络安全为人民 网络安全靠人民——智能时代 网安护航”为核心主题,聚焦智能化时代网络安全防护体系建设,强调全民、全行业、全场景的网络安全防护理念。随着人工智能、数字化办公、云终端、移动办公的全面普及,… · 2026/9/24 17:28:17
基于YOLOv8的渔船作业监控系统:从环境搭建到边缘部署全流程 简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的毕业设计级项目资源,围绕YOLOv8实现渔船作业监控系统,可用于毕设、课程设计、大作业或项目立项演示。压缩包共97个文件,约24.21MB,以70个Python源码文件为… · 2026/9/24 0:00:13
1D-CNN时间序列建模实战:从Conv1d原理到工业落地 简介:面向时间序列数据建模的一维卷积神经网络完整实现,适合深度学习入门者及需要快速验证时序模型的研究者,能够从音频、文本、传感器或股价等序列中挖掘局部特征与时间依赖。压缩包体积很小,只有3KB,内含3个Python脚… · 2026/9/24 0:00:26
柔软的L:汉语语流中被忽视的舌肌张力控制 1. 这个“L”不是字母表里的L,而是舌尖上的L最近在几个方言群和语音教学社群里,反复看到有人发一句:“也说字母L:柔软的长舌”。初看以为是英语发音课笔记,点开才发现全是方言爱好者、播音系学生、语言康复师甚至戏曲演… · 2026/9/24 0:00:44