编程语言语言运行时标准库垃圾回收并发编程【免费下载链接】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),仅供参考