资讯动态

仓颉 unittest.mock 验证 API 完全指南:用 @Called 与 Verify 精确断言 mock 交互

发布时间:2026/10/15 3:34:55 来源:尧图企业网站定制
文档知识库教程OpenHarmony【免费下载链接】CangjieCorpus本仓库为仓颉编程语言语料库聚焦 仓颉编程语言 核心知识体系整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源经结构化处理与专业标注构建为支持 RAG检索增强生成技术 的高质量知识基座适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用推动鸿蒙开发生态智能化升级项目地址https://gitcode.com/Cangjie/CangjieCorpus点击查看免费下载验证Verification是仓颉语言std.unittest.mock框架中与打桩Stubbing并列的核心能力。它基于 mock/spy 对象在测试执行期间留下的调用日志对是否发生了某次调用、调用了多少次、是否以特定参数调用、调用是否按特定顺序发生等交互行为运行断言。本文以 mock_framework_verification.md 为骨架结合 unittest_mock 包 API 定义中的Verify、VerifyStatement、OrderedVerifier、UnorderedVerifier等类型细节系统讲解验证语句、验证块、有序/无序验证、验证错误以及预期 vs 验证的取舍帮助你在仓颉单元测试中写出交互断言精确、意图清晰的测试用例。验证 API 能做什么验证 API 是 mock 框架的一部分其功能如下验证是否进行了某些调用。验证特定调用的次数。验证是否使用特定参数进行调用。验证调用是否按特定顺序进行。验证通过检查在执行测试期间构建的调用日志来运行断言。调用日志涵盖让mock和spy对象以及静态成员和顶层函数和顶层变量在测试中可访问的所有调用。只能验证在 mock/spy 对象以及静态成员和顶层函数和顶层变量上进行的调用——也就是说普通对象的真实调用不会进入日志自然也无法被验证。从 API 结构看见 unittest_mock_package_classes.md 中的 Verify 类验证体系由两个入口组成Verify类是验证 API 的入口提供that、ordered、unordered、noInteractions、clearInvocationLog等静态方法用于构建验证块Verification Block。Called宏用于构建关于代码的断言——它接受一个桩签名形式的调用表达式生成一条验证语句VerifyStatement即根据调用日志检查代码的单个断言。let foo mockFoo() //配置foo On(foo.bar()).returns() foo.bar() Verify.that(Called(foo.bar())) // 验证bar至少被调用一次提示Called宏与配置桩所用的On宏同属std.unittest.mock.mockmacro包见 宏文档。使用 mock 框架前需要导入std.unittest.mock.*与std.unittest.mock.mockmacro.*详见 mock_framework_getting_started.md。验证语句和Called宏验证语句由VerifyStatement类表示。VerifyStatement实例由Called宏创建且仅能由Called宏创建见 VerifyStatement 类定义。Called宏调用接受桩签名类似于On宏并且适用参数匹配器的规则。也就是说你既可以在验证语句中写死某个具体参数值也可以使用_等价于any()、eq(...)、ofTypeT()、argThat(...)等匹配器来放宽匹配条件Called(foo.bar(1, _)) // 匹配 bar 方法调用的验证语句其中第一个参数为 1 Called(Foo.baz) // 匹配 baz 静态属性 getter 调用的验证语句注意两条约束来源见 Matchers.capture 说明与 mock_framework_stubs.md值监听器ValueListener不允许在Called的参数范围内使用——即capture(...)这类捕获型匹配器不能用于验证语句。验证语句中的调用必须引用 mock/spy 对象或静态成员/顶层函数/顶层变量并且每个验证语句必须明确只匹配一个调用不允许模糊匹配详见下文验证错误中的不相交的语句。基数函数约束调用次数VerifyStatement类提供的 API 类似于桩配置时可用的基数说明符见 CardinalitySelector用于为一条验证语句指定期望的执行次数。基数函数为once()atLeastOnce()times(expectedTimes: Int64)times(min!: Int64, max!: Int64)atLeastTimes(minTimesExpected: Int64)never()调用这些函数会返回相同的VerifyStatement实例。同一语句不能重置基数且必须在语句传递到验证块生成器函数之前设置基数。如果没有显式设置基数则使用默认基数默认为依据验证块类型而定ordered块默认once()unordered块默认atLeastOnce()详见下文。Verify.that(Called(foo.bar()).atLeastOnce()) Verify.that(Called(foo.bar()).once())实现细节从VerifyStatement类定义可见once()、atLeastOnce()、times(...)、atLeastTimes(...)均返回VerifyStatement自身若对同一对象连续调用多个基数函数或语句已被传入验证动作框架会抛出MockFrameworkException传入负数的次数则抛出IllegalArgumentException。验证块验证块通常包含一个或多个验证语句且检查块中的语句会构成更复杂的断言。其行为遵循以下规则在调用验证块时会立即验证不验证之后发生的任何调用。验证块不会改变调用日志的状态日志中的每个调用都可以被任意数量的块检查。独立检查各个块前后块之间没有依赖关系。除非在块之间发生了一些调用或者手动清除了调用日志否则调用验证块的顺序并不重要。验证语句本身不执行任何类型的验证必须传递到验证块中才能生效。单个验证块仅检查在块内验证语句中提到的 mock/spy 对象上的调用忽略对其他对象的调用。Verify类包含几个构建验证块的静态方法。有序验证块用于检查调用的确切顺序无序验证块只验证调用的次数。有序ordered如需检查一个或多个对象的调用顺序使用ordered验证块生成器。ordered静态函数接收一个验证语句数组for (i in 0..4) { foo.bar(i % 2) } Verify.ordered( Called(foo.bar(0)), Called(foo.bar(1)), Called(foo.bar(0)), Called(foo.bar(1)) )允许检查多个对象的调用顺序for (i in 0..4) { if (i % 2 0) { fooEven.bar(i) } else { forOdd.bar(i) } } Verify.ordered( Called(fooEven.bar(0)), Called(fooOdd.bar(1)), Called(fooEven.bar(2)), Called(fooOdd.bar(3)), )有序验证的默认基数说明符是once()。如有需要可使用其他基数说明符for (i in 0..4) { foo1.bar(i) } for (i in 0..4) { foo2.bar(i) } Verify.ordered( Called(foo1.bar(_).times(4)), Called(foo2.bar(_).times(4)) )对于有序验证须列出对块中提到的mock/spy 对象的所有调用。任何未列出的调用都会导致验证失败foo.bar(0) foo.bar(10) foo.bar(1000) Verify.ordered( Called(foo.bar(0)), Called(foo.bar(10)) )输出验证失败 以下调用未匹配到任何语句 foo.bar(...) at example_test.cj:6实现细节从 ordered 函数定义可见ordered验证模式固定为exhaustive全量匹配并要求验证语句之间不相交——即单个调用行为不得同时匹配多个验证语句否则抛出异常。无序unordered无序验证只检查其验证语句的调用次数。对于无序验证除非显式指定否则使用atLeastOnce()基数即检查至少进行了一次的调用for (i in 0..4) { foo.bar(i % 2) } //验证是否至少调用了一次使用参数0和参数1的bar Verify.unordered( Called(foo.bar(0)), Called(foo.bar(1)) ) //验证是否调用了两次使用参数0和参数1的bar Verify.unordered( Called(foo.bar(0)).times(2), Called(foo.bar(1)).times(2) ) //验证是否总共调用了四次bar Verify.unordered( Called(foo.bar(_)).times(4) )无序验证包括Partial和Exhaustive两种模式对应 Exhaustiveness 枚举默认为Exhaustive需要列出验证语句所提到的 mock/spy 对象的所有调用。Partial只列出部分调用。for (i in 0..4) { foo.bar(i) } //失败foo.bar()的两次调用未在块中列出 Verify.unordered( Called(foo.bar(0)).once(), Called(foo.bar(1)).once() ) //忽略无关调用 Verify.unordered(Partial, Called(foo.bar(0)).once(), Called(foo.bar(1)).once() )实战提示当被测对象内部存在你无法精确控制顺序的调用例如绘制流程中3 个点和 3 条线的绘制顺序不确定时使用Partial模式或Verify.that(...)逐个断言可以避免因无关调用顺序而误报失败当你需要确保没有任何遗漏调用时则使用默认的Exhaustive模式。动态构建验证块ordered和unordered函数为接受 lambda 的重载函数。可使用checkThat(statement: VerifyStatement)函数动态添加语句。该机制对应 OrderedVerifier 与 UnorderedVerifier 两个收集器类型checkThat返回收集器对象自身便于连续调用。let totalTimes 40 for (i in 0..totalTimes) { foo.bar(i % 2) } Verify.ordered { v for (j in 0..totalTimes) { v.checkThat(Called(foo.bar(eq(j % 2)))) } }当验证语句的条数依赖于运行期计算的值如循环次数、数据规模时这种闭包 checkThat的动态构建方式比手写定长数组更灵活、更可维护。其他 API另外Verify类还提供了以下工具that(statement: VerifyStatement)为Verify.unordered(Partial, statement)的别名用于检查单个语句不需要列出对应 mock/spy 对象的所有调用。这是最常用的单条断言入口。noInteractions(mocks: ArrayObject)用于检查没有进行调用的 mock/spy 对象对应 noInteractions 定义。clearInvocationLog()将日志重置为空状态。这会影响后面的所有验证块但并不影响桩预期——已配置的桩及其预期仍然有效。示例foo.bar() Verify.that(Called(foo.bar())) // OK Verify.noInteractions(foo) // 失败foo.bar() 调用在日志中 Verify.clearInvocationLog() // 清除日志 Verify.noInteractions(foo) // 从日志中清除所有与 foo 的交互 Verify.that(Called(foo.bar())) // 失败应用场景clearInvocationLog()可用于分阶段验证。比如测试一个带缓存的仓库时先验证第一轮 10 次读取只触发了一次底层查询清除日志后修改失效条件再验证第二轮读取同样只触发一次底层查询见下文完整示例。它只清空调用日志不会撤销已定义的桩因此适合在同一测试用例中复用桩、分阶段观察交互。Verify类 API以下为Verify类的完整静态方法签名来源unittest_mock_package_classes.mdpublic static func that(statement: VerifyStatement): Unit public static func unordered( exhaustive: Exhaustiveness, collectStatements: (UnorderedVerifier) - Unit ): Unit public static func unordered( collectStatements: (UnorderedVerifier) - Unit ): Unit public static func unordered(statements: ArrayVerifyStatement): Unit public static func unordered( exhaustive: Exhaustiveness, statements: ArrayVerifyStatement ): Unit public static func ordered( collectStatements: (OrderedVerifier) - Unit ): Unit public static func ordered(statements: ArrayVerifyStatement): Unit public static func clearInvocationLog(): Unit public static func noInteractions(mocks: ArrayObject): Unit从中可以看出重载设计的规律数组形式statements: ArrayVerifyStatement用于静态列举验证语句仓颉的变长参数语法还支持省略[]因此可直接写成Verify.unordered(Called(...), Called(...))。闭包形式collectStatements: (...) - Unit用于在闭包内通过checkThat动态添加语句。unordered系列额外提供带exhaustive: Exhaustiveness参数的版本用于显式指定Exhaustive或Partial验证模式省略该参数时默认Exhaustive。ordered系列固定为全量匹配exhaustive因此没有Exhaustiveness参数。验证错误验证失败时会抛出VerificationFailedException见 异常定义mock 框架会给出报告。不要捕获该异常——验证失败应当直接导致测试失败捕获它会掩盖交互断言的问题。失败类型如下调用次数太少或调用次数太多调用次数与块中的语句不匹配。语句不匹配块中存在与日志中的调用不匹配的语句。调用不匹配日志中存在与块中的语句不匹配的调用。意外调用有序验证块需要的是其他的调用。无用交互noInteractions检测到意外调用。还有另一种失败类型不相交的语句Disjoint Statements不一定是测试代码本身有问题。调用匹配到多个语句时就会上报这种失败类型。在单个验证块中使用具有不相交参数匹配器的语句可能会导致此错误。不允许在语句和调用之间进行模糊匹配。典型反例Verify.unordered( Called(foo.bar(_)).times(7), // 宽匹配器会覆盖下面的细匹配器 Called(foo.bar(ofTypeDot())).times(3) )foo.bar(_)能匹配所有调用其中自然包含Dot类型参数的调用两条语句发生重叠。正确的做法是拆成多个Verify.that(...)单独断言见下文验证带特定参数的调用示例。示例和模式使用验证 API 的常见模式是验证测试代码无论是函数、类还是整个包与外部对象之间的交互。典型流程创建spy对象。将这些 spy 对象传递给测试代码。验证代码和 spy 对象之间的交互。验证调用数量下面的测试用例验证一个带缓存的仓库CachedRepository第一次连续读取 10 次只应触发一次底层查询清除调用日志并让缓存失效后再验证第二轮读取同样只查询一次func testDataCaching() { // 创建必要的spy或mock对象 let uncachedRepo spy(Repository()) let invalidationTracker mockInvalidationTracker() On(invalidationTracker.getTimestamp()).returns(0) // 准备测试数据 let cachedRepo CachedRepistory(uncachedRepo, invalidationTracker) // 运行测试代码 for (i in 0..10) { cachedRepo.get(TEST_ID) } // 验证得出只查询了一次基础repo没有对未缓存repo的其他调用 Verify.unordered(Exhaustive, Called(uncachedRepo.get(TEST_ID)).once() ) // 清除日志 Verify.clearInvocationLog() // 设置其他行为 On(invalidationTracker.getTimestamp()).returns(1) for (i in 0..10) { cachedRepo.get(TEST_ID) } // 自上次清除后只进行了一次调用 Verify.unordered(Exhaustive, Called(uncachedRepo.get(TEST_ID)).once() ) }这里Exhaustive模式同时保证了只查询了一次且没有其他对uncachedRepo的调用是验证缓存逻辑的高价值断言。验证带特定参数的调用当需要按参数类型区分调用时可以结合ofTypeT()与argThat(...)匹配器func testDrawingTriangle() { // 创建必要的 spy 或 mock 对象 let canvas spy(Canvas()) // 运行代码 canvas.draw(Triangle()) // 测试三角形由3条线和3个点组成 // 使用 that 块 Verify.that( Called(canvas.draw(ofTypeDot())).times(3) ) Verify.that( Called(canvas.draw(ofTypeLine())).times(3) ) // 或者使用部分无序验证块 Verify.unordered(Partial, // 未知线条和点实际绘制的顺序 Called(canvas.draw(ofTypeDot())).times(3), Called(canvas.draw(ofTypeLine())).times(3) ) // 使用枚举块时必须枚举出所有调用 Verify.unordered(Exhaustive, Called(canvas.draw(ofTypeTriangle())).once(), Called(canvas.draw(ofTypeDot())).times(3), Called(canvas.draw(ofTypeLine())).times(3) ) // 验证用例从未调用入参为 Square 类型的 draw 函数 Verify.that( Called(canvas.draw(ofTypeSquare)).never() ) // 如果想通过更复杂的条件来区分参数 // 可以使用下面的模式 let isDot { f: Figure f is Dot // 此为更复杂的逻辑 } Verify.that( Called(canvas.draw(argThat(isDot))).times(3) ) // 注意属于同一个块的语句必须明确只匹配了一个调用 // 以下为反例有些调用匹配了两个语句 Verify.unordered( Called(canvas.draw(_)).times(7), Called(canvas.draw(ofTypeDot())).times(3) ) }这个示例同时展示了never()的用法断言某个参数路径从未被调用、argThat自定义谓词匹配以及同一块内语句必须不相交的反例。验证调用顺序验证跨多个城市的一次航班行程构建过程调用必须严格按照起飞/降落顺序发生func testBuildFlight() { let plane spy(Plane()) FlightBuilder(plane).planFlight(Shenzhen, Shanghai, Beijing).execute() Verify.ordered( Called(plane.takeOffAt(Shenzhen)), Called(plane.landAt(Shanghai)), Called(plane.takeOffAt(Shanghai)), Called(plane.landAt(Beijing)) ) }预期与验证 API配置桩时可以设置预期expectation和验证 API 一样覆盖测试代码的一些断言。这两种方式各有侧重预期依托桩配置的基数说明符如once()、times(n)在测试结束后自动检查验证 API 则在调用点立即检查调用日志。这种情形下别无他法只能选择更能反映测试意图的方法。一般情况下建议避免重复验证块中的配置步骤——如果桩本身已经带预期就不必再写一份等价验证let foo mockFoo() On(foo.bar(_)).returns() // 如果从未使用此桩测试失败 foo.bar(1) foo.bar(2) Verify.that( // 不需要自动验证 Called(foo.bar(_)).atLeastOnce() ) //但可以检查调用的数量和具体的参数 Verify.unordered( Called(foo.bar(1)).once(), Called(foo.bar(2)).once() )上面的示例可以使用预期重写let foo mockFoo() On(foo.bar(1)).returns().once() // 预期只被调用一次参数为1 On(foo.bar(2)).returns().once() // 预期只被调用一次参数为2 foo.bar(1) foo.bar(2) //如果没有桩被触发则测试失败选型建议只关心某个桩是否被触发、触发几次→ 用预期基数说明符代码更少、自动检查。需要按参数拆分断言调用次数、断言从未调用某路径never()、校验跨对象调用顺序、分阶段清除日志后重新验证→ 用验证 API。两者结合时避免重复同一事实不要既写桩预期又写验证语句。详细的默认预期对照表可参考 mock_framework_basics.md 的预期章节。小结仓颉std.unittest.mock的验证 API 以Called宏生成验证语句、以Verify类的静态方法组织验证块that适合单条断言unordered适合只关心次数的交互检查可选Partial/Exhaustive模式ordered适合强顺序约束clearInvocationLog支持分阶段验证noInteractions用于断言零交互。验证失败统一抛出VerificationFailedException且语句之间必须保持不相交。理解验证块基于调用日志、立即执行、不修改日志、只检查块内提及对象等运行机制再结合预期优先、验证补充、避免重复的选型原则即可在仓颉单元测试中写出精确表达交互意图的高质量测试代码。赞分享文档知识库教程OpenHarmony【免费下载链接】CangjieCorpus本仓库为仓颉编程语言语料库聚焦 仓颉编程语言 核心知识体系整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源经结构化处理与专业标注构建为支持 RAG检索增强生成技术 的高质量知识基座适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用推动鸿蒙开发生态智能化升级项目地址https://gitcode.com/Cangjie/CangjieCorpus点击查看免费下载相关推荐Chokidar v5源码深读FSWatcher如何管理文件监听的完整生命周期Chokidar v5源码深读FSWatcher如何管理文件监听的完整生命周期 Chokidar 是一个极简、高效的跨平台 文件监听库 本文带你深读 v5编程语言语言运行时标准库垃圾回收并发编程仓颉Cangjiemock 框架验证 API 实战指南用 Called 与 Verify 校验调用次数、参数与顺序仓颉Cangjiemock 框架验证 API 实战指南用 Called 与 Verify 校验调用次数、参数与顺序 mock 框架的 验证 API 是仓编程语言语言运行时标准库垃圾回收并发编程Token Monitor 架构深潜Local-First 设计下Collector 管线如何实现 3-5 秒实时刷新Token Monitor 架构深潜Local First 设计下Collector 管线如何实现 3 5 秒实时刷新 Token Monitor 是一款本文档知识库教程OpenHarmony上一篇WindowResizer窗口缩放工具锁死的窗口也能一键改尺寸下一篇告别每180天重新激活KMS_VL_ALL_AIO Windows/Office KMS激活脚本 3步完整讲清创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑