cangjie_runtime std.unittest.mock 构造函数全解析:mock\<T>、mock\<T>(StubMode) 与 spy\<T> 的用法与实现
编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】cangjie_runtime仓颉编程语言运行时与标准库。项目地址https://gitcode.com/Cangjie/cangjie_runtime点击查看免费下载本文以仓颉标准库单元测试 mock 框架std.unittest.mock的三个核心构造函数——mockT()、mockT(modes: ArrayStubMode)与spyT(objectToSpyOn)为主题结合cangjie_runtime仓库中的文档与源码讲解 mock 对象与 spy 对象的创建方式、桩模式StubMode的语义、源码级实现原理以及在不同编译方式cjc / cjpm下的启用条件。读完本文你将掌握如何在仓颉单元测试中正确创建和配置 mock/spy 对象并理解这些 API 背后会话Session 调用处理器CallHandler 桩Stub的运行机制。一、三个构造函数速览mock 框架提供了创建两种测试替身对象的入口mock与spy均由std.unittest.mock包导出。三者的签名如下摘自 stdlib/doc/libs/std/unittest_mock/unittest_mock_package_api/unittest_mock_package_functions.mdpublic func mockT(): T public func mockT(modes: ArrayStubMode): T public func spyT(objectToSpyOn: T): T函数功能默认行为mockT()创建类型T的 mock 对象所有成员函数、属性或运算符重载函数没有任何具体实现mockT(modes)创建类型T的 mock 对象并指定桩的模式同上但未打桩调用可回退到模式定义的默认行为spyT(objectToSpyOn)创建类型T的 spy 对象包装传入实例成员调用默认委托给该实例的对应实现三者都遵循同一使用前提只有类包括 final 类和 sealed 类和接口支持通过这种方式创建 mock/spy 对象详见 mock 基础概念及用法。创建后成员行为一律通过On宏打桩指定On宏位于std.unittest.mock.mockmacro包中因此使用前需要同时导入两个包import std.unittest.mock.* import std.unittest.mock.mockmacro.*二、mockT()创建无行为的骨架对象2.1 签名与返回值public func mockT(): T功能创建类型T的 mock 对象。这个对象默认情况下所有的成员函数、属性或运算符重载函数没有任何具体实现——即未打桩的调用会被视为错误。行为需要通过On宏指定。返回值类型T的 mock 对象。2.2 完整示例以下示例来自 API 文档标记为!-- run --即文档中可实际编译运行的用例import std.unittest.mock.* class Generator { public func generate(): Int { return 0 } } Test func test() { let generator mockGenerator() // generator.generate() // expected to fail (not stubbed yet) }注意其中注释的语义mockGenerator()创建的对象在未打桩时调用generate()会失败。这正是 mock 与 spy 的关键区别——mock 对未打桩调用抛出UnstubbedInvocationException未提供与此调用匹配的桩而 spy 会委托给底层实例。2.3 源码实现create工厂与会话注册从源码 stdlib/libs/std/unittest/mock/create.cj 可以看到mockT()与mockT(modes)的实现都收敛到同一个create工厂函数区别仅在于MockKind与桩模式数组Frozen public func mockT(): T { return createT(Mock, []) { handler: CallHandlerImpl unsafe { createMockT(handler) }} } Frozen public func mockT(modes: ArrayStubMode): T { return createT(Mock, modes) { handler: CallHandlerImpl unsafe { createMockT(handler) }} }而create的内部流程create.cj如下func createT(kind: MockKind, modes: ArrayStubMode, constructor: (CallHandlerImpl) - T): T { MockFramework.session { session: MockSession let id _FRAMEWORK.generateUniqueId() let createdAtSessionName session.name let handler CallHandlerImpl(id, createdAtSessionName) let objAsT constructor(handler) let ref (objAsT as Object) ?? internalError(Mock object does not inherit Object superclass) _FRAMEWORK.registerObject(MockObject(id, ref, kind, modes, createdAtSessionName)) objAsT } }可以提炼出以下关键实现事实会话Session内创建create必须在MockFramework.session会话块内执行mock 对象归属于创建它的会话。会话形成一个栈式结构测试框架会隐式地为每个测试用例打开/关闭会话源码中的会话前缀常量testCaseSessionPrefix test case 等佐证了这一点见 stdlib/libs/std/unittest/mock/framework.cj。唯一标识 调用处理器每个对象通过_FRAMEWORK.generateUniqueId()获得全局唯一的UInt64标识并绑定一个CallHandlerImpl调用处理器。成员调用经由编译器内建机制路由到该处理器从而被桩匹配逻辑拦截。对象注册表MockObject(id, ref, kind, modes, sessionName)被登记到会话的对象注册表objectRegistry见 stdlib/libs/std/unittest/mock/mock_session.cj其中MockKind只有Mock | Spy两个取值modes即桩模式数组。真正对象的生成依赖内建函数createMockT(handler)与createSpyT(handler, objectToSpyOn)声明在 stdlib/libs/std/unittest/mock/internal/instrinsics.cj带有Intrinsic注解说明对象骨架的实际生成由编译器/运行时内建能力完成是 mock 框架能够伪造任意类/接口实例的底层基础。三、mockT(modes)用桩模式简化配置3.1 签名与参数public func mockT(modes: ArrayStubMode): T功能创建类型T的 mock 对象参数指定了桩的模式。参数modes: ArrayStubMode指定桩的模式可以同时传多个如mockFoo(ReturnsDefaults, SyntheticFields)。返回值类型T的 mock 对象。3.2 完整示例import std.unittest.mock.* import std.unittest.mock.mockmacro.* interface Api { func request(s: String): String } Test func test() { let api mockApi(ReturnsDefaults) Expect(api.request(something), ) On(api.request(cookie)).returns() On(api.request(pizza)).returns() On(api.request(salad)).returns() Expect(api.request(cookie), ) Expect(api.request(pizza), ) Expect(api.request(salad), ) // api.request(pasta) // expected to fail }这个示例展示了桩模式与显式桩的组合ReturnsDefaults让未打桩的request(something)返回默认空字符串因此第一行Expect可以通过而On显式打桩的cookie/pizza/salad三个参数则返回各自配置的值。显式桩始终优先于默认行为。3.3 桩模式StubMode的两种取值StubMode枚举定义在 stdlib/libs/std/unittest/mock/create.cj含两个成员模式语义源码注释要点ReturnsDefaultsmock 对象为常见简单类型返回默认值用于简化配置支持的返回类型包括Unit、数值类型、Option、Bool、String、Array、ArrayList、HashSet、HashMap默认值为负值或空值SyntheticFieldsmock 对象把其可变属性和字段当作可变字段对待等价于使用SyntheticField但更简洁读取未初始化的字段将导致错误ReturnsDefaults的默认值明细如下见 桩使用指南 · 桩的模式类型默认值Boolfalse数值类型numbers0String空字符串OptionNoneArrayList、HashSet、Array新的空集合HashMap新的空 map关于两种模式的组合使用枚举文档 中给出了mockEntityGenerator(ReturnsDefaults, SyntheticFields)的完整用例未赋值的String属性返回赋值后读回赋值内容——当同时启用SyntheticFields与ReturnsDefaults时已赋的值优先于默认值但字段/属性尚未被赋值前仍可使用默认值。3.4 使用桩模式的注意事项桩模式可用于减少配置代码可以与显式桩自由组合但显式桩始终优先于默认行为使用桩模式不会对 mock 对象的成员强加任何期望不会因成员未被调用而失败因此当测试需要严格校验仅调用某些特定成员时需谨慎使用当前桩模式不支持打桩静态成员和顶层函数/变量这些语义在 stdlib/libs/std/unittest/mock/create.cj 的枚举注释中均有明确说明。四、spyT(T)包装真实实例的活体对象4.1 签名与参数public func spyT(objectToSpyOn: T): T功能创建类型T的 spy 对象mock 对象的扩展成员拥有默认实现的骨架对象。这个对象包装了传入的对象默认情况下成员函数、属性或运算符重载函数实现为对这个传入实例对象对应成员的调用。可以通过On重载覆盖这个对象成员的行为。参数objectToSpyOn: T传入的实例对象默认情况下使用该对象的实现。返回值类型T的 spy 对象。4.2 完整示例import std.unittest.mock.* import std.unittest.mock.mockmacro.* class GreetingBuilder { func date(): String { January 1st } func greet(name: String): String { Hello, ${name}. Today is ${date()}. } } Test func test() { let originalBuilder GreetingBuilder() let builder spy(originalBuilder) Expect(builder.date(), January 1st) Expect(builder.greet(Jack), Hello, Jack. Today is January 1st.) On(builder.date()).returns(February 29th) Expect(builder.date(), February 29th) Expect(builder.greet(Mary), Hello, Mary. Today is February 29th.) }这个示例非常直观地展示了 spy 的先委托、后覆盖特性创建 spy 后、尚未打桩时date()与greet(Jack)都调用originalBuilder的真实实现输出为原始日期字符串通过On(builder.date()).returns(February 29th)覆盖date()后直接调用builder.date()返回新值而greet(Mary)内部仍调用date()但由于 spy 只拦截经由 spy 引用的调用greet内部对this.date()的调用并未被拦截因此输出中仍是原始日期。4.3 mock 与 spy 的行为对比场景mock 对象spy 对象成员调用未匹配到任何桩抛出未打桩调用错误测试失败调用被监视实例的原始成员通过On配置定义行为重写覆盖真实实例的可观察行为底层实例无骨架对象由spy(instance)传入spy 的静态成员/顶层声明打桩行为也类似 spy未打桩时调用原始成员/原函数而非像 mock 那样抛异常见 mock 基础概念及用法。此外需要注意只有通过 spy 对象引用的调用才会被拦截创建 spy 对象对原始实例的引用没有影响spy 内部调用自己的方法不会被拦截。典型的 spy 用法是模拟超时继续使用真正的实现let serviceSpy spy(service) // 模拟超时继续使用真正的实现 On(serviceSpy.request()).throws(TimeoutException()).once().then().callsOriginal() // 测试代码必须使用 serviceSpy 引用这里用到了throws(...).once().then().callsOriginal()的桩链能力第一次调用抛超时异常后续调用回到原始实现。4.4 源码实现spyT在源码中的实现同样收敛到create工厂只是MockKind换为Spy且构造函数多携带了被监视的实例create.cjFrozen public func spyT(objectToSpyOn: T): T { return createT(Spy, []) { handler: CallHandlerImpl unsafe { createSpyT(handler, objectToSpyOn) }} }从MockKind的取值Mock | Spy见 mock_session.cj可以看出mock 与 spy 在框架内部是同一套注册、拦截、匹配机制的两种形态默认行为的差异mock 报错 vs spy 委托由框架的调用处理逻辑依据 kind 区分。同时CallHandlerImpl与CallHandler接口见 framework.cj 及Mocked接口 stdlib/libs/std/unittest/mock/internal/mocked.cj共同构成了调用拦截的通道——这正是On桩能够精确匹配到哪个对象、哪个成员、哪些参数的基础设施。五、配套使用On宏与桩机制创建 mock/spy 对象只是第一步真正定义行为的是配置 API入口即On宏。其宏实现位于 stdlib/libs/std/unittest/mock/mockmacro/macros.cjpublic macro On(tokens: Tokens): Tokens { parseAndGenerate(tokens) { stubCall, description match (stubCall.kind) { case Function generateStubFunction(stubCall, description) case Getter generateStubGetter(stubCall, description) case Setter(getterCall) generateStubSetter(stubCall, getterCall, description) } } }可以看到On宏在编译期解析桩签名并按实体种类函数 / 属性 getter / 属性 setter生成对应的桩代码。桩的匹配与执行遵循如下顺序见 桩使用指南查找特定声明的桩后声明的桩优先于之前声明的桩测试用例主体内声明的桩优先于共享桩应用每个桩的参数匹配器Matchers静态函数如any()、eq(value)、same(reference)、ofTypeT()、argThat(predicate)、none()全部参数匹配则执行桩操作找不到桩时执行默认行为mock 报未打桩调用错误spy 调用原始成员静态/顶层声明调用原始对应声明。On可配置的行为操作 API包括returns(value)、returnsConsecutively(...)、throws(exception)、fails()、doesNothing()setter 专用以及 spy 专用的callsOriginal()/getsOriginal()/setsOriginal()还可通过基数说明符once()、atLeastOnce()、anyTimes()、times(n)等声明预期调用次数。六、编译与运行前提mock 框架是仓颉标准库单元测试的一部分启用方式取决于构建工具详见 mock 框架入门 与 mock 基础概念及用法 · mock 依赖cjpm仅需运行cjpm test工具会自动检测 mock 使用并生成正确的 cjc 调用确保可以从任何从源代码编译的包中 mock 类也可通过 cjpm 配置文件控制哪些包支持 mock。cjcmock 能力由--mock标志控制。若想 mock 特定包中的类p在调用 cjc 时添加--mockon编译依赖p的包时也必须添加此标志。在测试中使用 mock 对象cjc --test则不需要额外标志。接口与类的差异接口始终可以被 mock从另一个包 mock 一个类时类本身及其超类必须按特定方式编译——例如只能 mock 预编译库如 stdlib中的接口不能 mock 其中的类。上述约束说明mock 类的支持与编译期元数据密切相关这正是createMockT/createSpyT作为Intrinsic内建函数、依赖编译器配合的原因。七、小结API适用场景关键点mockT()完全隔离依赖为特定测试场景配置行为未打桩调用即失败行为全部由On定义mockT(modes)需要大量默认行为的 mockReturnsDefaults返回类型默认值SyntheticFields模拟可变字段显式桩优先spyT(obj)需要基于真实实现做局部覆盖未打桩时委托给obj原始实现只有经 spy 引用的调用被拦截在 stdlib/libs/std/unittest/mock 目录下可以继续研读create.cj构造函数与桩模式定义、framework.cj会话与调用处理、mock_session.cj对象注册表与桩链存储、macros.cjOn/Called宏等源码结合 unittest_mock_samples 下的入门、基础概念、桩使用指南与验证文档即可完整掌握仓颉 mock 框架的实战能力。赞分享编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】cangjie_runtime仓颉编程语言运行时与标准库。项目地址https://gitcode.com/Cangjie/cangjie_runtime点击查看免费下载相关推荐Bitwarden Clients 测试规范用 jest-mock-extended 的 mock\T\() 替代手写 Mock 实现Bitwarden Clients 测试规范用 jest mock extended 的 mock\T\ 替代手写 Mock 实现 导读 本文基于仓库内 .前端桌面应用CLI应用安全Aptos Core Rebase 指南处理 cached-packages 框架工件冲突的完整实战Aptos Core Rebase 指南处理 cached packages 框架工件冲突的完整实战 导读 在 Aptos 仓库aptos core中修改开发工具Presto T-Digest 函数完全指南基于 t-digest 数据结构的近似分位数与分布估计Presto T Digest 函数完全指南基于 t digest 数据结构的近似分位数与分布估计 本指南系统讲解 Presto当前仓库 presto ma大数据数据库后端上一篇3个颠覆性技巧用Imba样式修饰符打造现代Web应用下一篇Android-Sunflower深色模式切换主题实现MaterialComponents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

2S串联电芯主动均衡方案:基于MP2642与STM32的BMS设计

2S串联电芯主动均衡方案:基于MP2642与STM32的BMS设计

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

2026/10/15 2:15:17 阅读更多 →
单片机C库运行时:libspace、多任务重入与中断安全实战

单片机C库运行时:libspace、多任务重入与中断安全实战

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

2026/10/15 2:15:17 阅读更多 →
Apache配置HTTP跳转HTTPS:两种稳定方案与常见坑解析

Apache配置HTTP跳转HTTPS:两种稳定方案与常见坑解析

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

2026/10/15 2:15:17 阅读更多 →

最新新闻

从源码编译GMSSL:国密算法支持与HTTPS链接调试实战

从源码编译GMSSL:国密算法支持与HTTPS链接调试实战

1. 从源码到动态库:为什么选择自己编译GMSSL很多人第一次接触国密算法开发时,第一反应是找现成的二进制包或者系统自带的包管理器直接安装。这个思路在大多数场景下没问题,但一旦涉及国密算法,情况就完全不一样了。GMSSL作为国内广…

2026/10/15 2:46:27 阅读更多 →
SpringBoot+Vue学院管理系统开发实战

SpringBoot+Vue学院管理系统开发实战

1. 项目概述与适用场景这个基于SpringBootVue技术栈的学院个人信息管理系统,是典型的教学级全栈开发项目。我在高校信息化部门工作期间,曾主导开发过类似系统,也指导过多名学生完成此类毕业设计。这类系统之所以成为热门选题,关键…

2026/10/15 2:46:27 阅读更多 →
SpringBoot+Vue高校医务室预约管理系统设计与实践

SpringBoot+Vue高校医务室预约管理系统设计与实践

1. 项目背景与核心价值高校医务室作为校园健康保障的第一道防线,长期面临着就诊高峰期排队混乱、病历管理低效、健康数据分散等痛点。这个基于SpringBootVue的预约管理系统,正是为了解决这些实际问题而设计的数字化解决方案。我在参与某高校医疗信息化改…

2026/10/15 2:46:27 阅读更多 →
医疗陪诊小程序开发:功能架构与技术实现详解

医疗陪诊小程序开发:功能架构与技术实现详解

1. 陪诊小程序的市场需求与产品定位医疗陪诊服务正在成为城市生活中的刚需。根据我过去三年参与医疗信息化项目的经验,独居老人、术后康复患者、异地就医人群构成了核心用户群体。这类用户往往面临"挂号难""流程陌生""行动不便"三大痛…

2026/10/15 2:46:27 阅读更多 →
KingdeeERP插件开发实战:从入门到精通

KingdeeERP插件开发实战:从入门到精通

1. 项目概述KingdeeERP作为国内主流的企业管理软件,其插件开发能力为系统功能扩展提供了强大支持。作为一名在ERP领域深耕多年的开发者,我见证了从K/3到Cloud版本的技术演进过程。插件开发不仅仅是简单的功能叠加,更是对企业业务流程的深度定…

2026/10/15 2:46:27 阅读更多 →
Flutter 欢迎页实战:用 intro_slider 构建引导页并实现「只显示一次」的路由跳转(Flutter-Notebook slider_screen 示例解析)

Flutter 欢迎页实战:用 intro_slider 构建引导页并实现「只显示一次」的路由跳转(Flutter-Notebook slider_screen 示例解析)

示例工程移动开发 【免费下载链接】Flutter-Notebook FlutterDemo合集,今天你fu了吗 项目地址: https://gitcode.com/gh_mirrors/fl/Flutter-Notebook 点击查看 免费下载 本文以 Flutter-Notebook 仓库中 mecury_project/example/slider_screen 示例为核…

2026/10/15 2:45:27 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/14 7:53:34 阅读更多 →