ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

AOSP 环境下的 Rust 单元测试 Mock 实战:基于 comprehensive-rust 课程使用 Mockall

AOSP 环境下的 Rust 单元测试 Mock 实战:基于 comprehensive-rust 课程使用 Mockall AOSP 环境下的 Rust 单元测试 Mock 实战基于 comprehensive-rust 课程使用 Mockall【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust本指南以 comprehensive-rust 课程 安卓测试Testing in Android 章节中的《Mocking》一课为核心系统讲解在 AndroidAOSP源码环境中用 Rust 编写单元测试时如何进行 Mock为什么需要将代码重构为 trait 抽象、如何使用#[mockall::automock]一键生成 Mock 实现、如何按调用参数设置差异化期望expectation以及为什么官方推荐能不用 Mock 就不用 Mock。读完本文你将在自己的 Android Rust 测试工程中熟练运用 Mockall 写出快速、稳定且贴近真实依赖行为的单元测试。为什么 AOSP 的 Rust 测试需要 Mock在 Android 源码树AOSP中编写 Rust 单元测试时被测代码常常依赖数据库、网络服务、系统服务等外部组件。直接实例化这些真实依赖不仅拖慢测试还会让测试结果受环境波动影响——网络超时、数据库状态残留都会让测试随机变红。Mock 的思路是用一份预先编排好行为的替身替换掉真实依赖让测试完全隔离于外部世界。课程明确指出Mockall 是 AndroidAOSP推荐的 Mock 库这一推荐在仓库中同样落地为具体的构建配置在 src/android/testing/Cargo.toml 中声明了mockall 0.15.0并在 Android.bp 与 BUILD.bazel 中分别定义了对应的rust_test与rust_test目标作为可编译、可运行的示例。Mockall 有一个关键前提Mock 的对象必须是 trait。因此使用 Mockall 的第一步往往是把具体实现重构成 trait 抽象。重构先行把依赖抽象成 traitMockall 无法直接 Mock 一个struct或一个具体函数它生成的 Mock 是某个 trait 的自动实现。所以你需要把被测代码所依赖的行为提炼为 trait例如课程示例中的Petuse std::time::Duration; #[mockall::automock] pub trait Pet { fn is_hungry(self, since_last_meal: Duration) - bool; }#[mockall::automock]是 Mockall 的核心宏它在编译期扫描 trait 定义自动生成一个名为MockPet的 Mock 结构体即原 trait 名加上Mock前缀以及与之配套的expect_*方法。此后被测代码只要泛化于Pet测试中就可以传入MockPet实例。这一重构为 trait → 自动生成 Mock的工作流正是课程强调的核心模式先抽象后 Mock。这既是 Mockall 的用法也是让代码更具可测试性的良好实践。第一个 Mock 测试用return_const固定返回值课程的入门示例源码见 src/android/testing/mockall.rs 的simple_example锚点展示了最小可用的 Mock 测试use std::time::Duration; #[mockall::automock] pub trait Pet { fn is_hungry(self, since_last_meal: Duration) - bool; } #[test] fn test_robot_dog() { let mut mock_dog MockPet::new(); mock_dog.expect_is_hungry().return_const(true); assert!(mock_dog.is_hungry(Duration::from_secs(10))); }拆解这段代码的调用链MockPet::new()创建一份全新的 Mock 实例expect_is_hungry()为is_hungry方法注册一个期望expectation此时 Mockall 尚不知道方法应当返回什么return_const(true)告诉 Mockall只要is_hungry被调用就固定返回true最后用普通assert!验证业务逻辑机器人狗 10 秒没吃饭确实饿了。注意到mock_dog声明为let mut——因为设置期望会改变 Mock 的内部状态。这一点与 Rust 惯用法一致凡是按参数返回不同结果的 Mock 配置都需要可变绑定。按参数设置期望用谓词让 Mock 更聪明固定返回值只解决了有替身的问题。真实世界中的依赖通常是有状态、有逻辑的参数不同结果就不同。Mockall 通过with(predicate)让期望与调用参数挂钩。课程用一只被喂食 3 小时后才会饿的猫来演示源码见 mockall.rs 的extended_example锚点#[test] fn test_robot_cat() { let mut mock_cat MockPet::new(); mock_cat .expect_is_hungry() .with(mockall::predicate::gt(Duration::from_secs(3 * 3600))) .return_const(true); mock_cat.expect_is_hungry().return_const(false); assert!(mock_cat.is_hungry(Duration::from_secs(5 * 3600))); assert!(!mock_cat.is_hungry(Duration::from_secs(5))); }这段代码设置了两个期望第一个期望要求参数满足gt(Duration::from_secs(3 * 3600))即距上次进食超过 3 小时返回true第二个期望没有with约束作为兜底默认值返回false。于是is_hungry(5 * 3600)命中超过 3 小时的期望得到true而is_hungry(5)落入默认期望得到false。这正是课程所说的期望可以依赖传入的参数从而用一份 Mock 模拟出真实的状态机行为。mockall::predicate模块还提供了丰富的谓词如eq、ne、lt、ge、le、in_iter、function等可以组合出任意复杂的参数匹配逻辑课程示例只用了其中最简单的gt。约束调用次数.times(n)与自动校验Mockall 的另一个重要能力是调用次数约束。课程特别指出你可以用.times(n)限制某个 Mock 方法最多被调用n次——如果最终不满足Mock 在被 drop 时会自动 panic。mock_dog.expect_is_hungry().times(2).return_const(true);这个特性的实战价值在于它能验证被测代码是否按预期次数调用了依赖。例如一段应该只发送一次网络请求的代码如果实际发了两三次测试会在 Mock 释放时立即失败从而把过度调用依赖这类 bug 挡在测试阶段。由于 panic 发生在 drop 时测试函数结束时 Mock 实例被析构校验自动触发无需额外断言。需要留意的是Mockall 不在 Rust Playground 中无法在线运行示例。课程建议在本地环境中使用cargo add mockall快速把 Mockall 加入既有 Cargo 工程这是最直接的起步方式。在当前仓库中实际运行 Mockall 示例comprehensive-rust 仓库把上述两个示例做成了三种构建系统都能跑的目标读者可以直接对照学习仓库只读以下路径均为查看与运行指引Cargo 工程src/android/testing/Cargo.toml 中声明了[[example]]段将mockall.rs注册为名为mockall-example的示例crate-type [staticlib]、test true依赖mockall 0.15.0SoongAOSP 主构建系统src/android/testing/Android.bp 定义了rust_test { name: libmockall_example, ... rustlibs: [libmockall], host_supported: true }表明该测试可跑在 host 上Bazelsrc/android/testing/BUILD.bazel 定义了rust_test(name mockall_example, size small, srcs [mockall.rs], deps all_crate_deps(normal True))。三种配置的核心信息是一致的mockall.rs既是示例代码也是测试代码Mockall 作为依赖被显式引入测试目标都支持 host 运行。这为如何在自己的 AOSP 或 Bazel 工程里添加 Mockall 测试提供了可直接照搬的模板。关于 Mock 的争议为什么官方建议优先用真实依赖课程在讲完 Mockall 用法后特意用一段泼冷水式的说明指出 Mock 的争议性这一点非常值得测试设计者认真对待Mock 的优点完全隔离依赖测试执行更快、结果更稳定——不依赖网络、不依赖数据库状态。Mock 的风险Mock 可能被配置错返回与真实依赖不同的结果从而测试通过、线上出错。Mock 验证的是替身行为不是真实行为。因此课程给出两条非常实用的替代建议优先使用真实依赖的轻量模式许多数据库支持配置 in-memory 后端测试既拿到正确行为又速度快、用完自动清理优先启动进程内服务器许多 Web 框架支持在localhost随机端口上启动进程内服务器用它测试代码比 Mock 掉整个框架更贴近真实环境。这条原则可以概括为Mock 是最后手段而不是默认手段。把依赖的正确性交给真实的轻量替身把调用关系与边界行为留给 Mockall是 AOSP Rust 测试的推荐姿势。与 GoogleTest 章节的互补关系在 src/android/testing 目录中Mockall 章节与 googletest.md 章节互为补充GoogleTest crate 负责提供基于matcher 的丰富断言如expect_that!、elements_are!以及多行字符串的 diff 输出而 Mockall 负责替身注入。两者结合使用正是 AOSP 上编写 Rust 单元测试的完整工具箱——Mock 负责假装依赖GoogleTest 负责精确断言。小结围绕 Mockall本课程给出的核心结论可以浓缩为四条抽象先行把依赖重构为 trait#[mockall::automock]自动生成MockXxx与expect_*按需配置return_const固定返回值with(predicate)按参数分流.times(n)约束调用次数drop 时自动校验环境先行Mockall 不在 Playground用cargo add mockall在本地工程引入当前仓库锁定版本为0.15.0真实优先能上内存数据库、进程内服务器就用真实的Mock 用于隔离和验证调用关系而非替代一切。按照这套方法你就能在 AOSP 与 Bazel/Cargo 工程中写出既快速稳定、又忠于真实行为的 Rust 单元测试。【免费下载链接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.项目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表