![Rustlings 测试练习精讲:用 assert!、assert_eq! 与 [should_panic] 写出真正能通过的单元测试](http://pic.xiahunao.cn/yaotu/Rustlings 测试练习精讲:用 assert!、assert_eq! 与 [should_panic] 写出真正能通过的单元测试)
Rustlings 测试练习精讲用 assert!、assert_eq! 与 #[should_panic] 写出真正能通过的单元测试【免费下载链接】rustlings:crab: Small exercises to get you used to reading and writing Rust code!项目地址: https://gitcode.com/gh_mirrors/ru/rustlingsRustlings 的17_tests练习组是整套练习中少有的“提前插队”内容exercises/17_tests/README.md 明确说明这一章刻意跳出 Rust 官方书籍的章节顺序先讲测试因为“后面的许多练习都会要求你让测试通过”原文Going out of order from the book to cover tests -- many of the following exercises will ask you to make tests pass!。本指南以这三道测试练习为主体完整拆解assert!、assert_eq!与#[should_panic]三种核心断言机制的用法、取值要求和失败表现并结合 Rustlings 自身的运行器源码说明“练习通过”的判定标准帮你把单元测试从会抄变成会写。一、练习布局三道题、三套断言机制以及 Rustlings 如何判定通过17_tests目录下的文件构成如下练习文件考察目标对应参考解exercises/17_tests/tests1.rsassert!条件断言solutions/17_tests/tests1.rsexercises/17_tests/tests2.rsassert_eq!值相等断言solutions/17_tests/tests2.rsexercises/17_tests/tests3.rs#[should_panic]预期 panic 测试solutions/17_tests/tests3.rs三道题共享同一个结构一个普通的main()函数注释写着“You can optionally experiment here”即可选实验区外加一个由#[cfg(test)]属性修饰的mod tests模块。#[cfg(test)]的含义是该模块只在cargo test编译时才存在平时运行cargo run不会包含它——这正是 Rust 单元测试的标准写法测试代码与生产代码同文件、同模块树但物理上被条件编译隔离。Rustlings 的练习校验也建立在这一机制上。从源码结构看运行器对每道练习执行的正是cargo testsrc/exercise.rs 中会丢弃一次编译输出、再调用cargo test …并检查其退出状态src/info_file.rs 的注释同样写明了对练习运行cargo test。因此这三道练习的“通过标准”不是cargo run成功而是测试模块里的每一个#[test]函数都必须编译成功且断言全部通过。这也是为什么三道题都在mod tests里留了空参数或todo!()——留空意味着编译失败或测试失败练习自然无法过关。练习注册关系可以在 dev/Cargo.toml 中查证tests1、tests2、tests3与其*_sol解答版都被声明为该 Cargo 包的 bin target分别指向exercises/17_tests/*.rs和solutions/17_tests/*.rs。二、tests1用 assert! 做条件断言并用 ! 取反exercises/17_tests/tests1.rs 的被测对象是一个最简单的函数fn is_even(n: i64) - bool { n % 2 0 }练习给出的骨架是#[cfg(test)] mod tests { // TODO: Import is_even. You can use a wildcard to import everything in // the outer module. #[test] fn you_can_assert() { // TODO: Test the function is_even with some values. assert!(); assert!(); } }它考察两个知识点导入被测函数。mod tests是外部模块的兄弟模块要用通配符导入外部模块的所有项。solutions/17_tests/tests1.rs 的解法是// When writing unit tests, it is common to import everything from the outer // module (super) using a wildcard. use super::*;super指mod tests的父模块即文件顶层use super::*把is_even等符号引入测试模块作用域。这是 Rust 官方书籍推荐、也是本仓库所有测试练习的统一写法。assert!的两种用法。assert!(expr)要求expr求值为true否则测试失败。参考解里用到了两条断言assert!(is_even(0)); assert!(!is_even(-1)); // ^ You can assert false using the negation operator !.第一条断言“0 是偶数”直接通过第二条验证is_even(-1)为false由于assert!只能断言真需要用逻辑非运算符!对函数结果取反再交给assert!。这正是注释强调的细节——断言一个函数“应该返回 false”方式是断言其否定。注意参数选取本身也隐含了边界意识0是非负偶数-1覆盖负数分支i64取模对负数结果的行为-1 % 2 -1不等于 0正好被这条用例间接验证。三、tests2用 assert_eq! 精确比较函数返回值exercises/17_tests/tests2.rs 的被测函数利用位移计算 2 的幂// Calculates the power of 2 using a bit shift. // 1 n is equivalent to 2 to the power of n. fn power_of_2(n: u8) - u64 { 1 n }题目要求在you_can_assert_eq中补全 4 条assert_eq!。与assert!只判断布尔不同assert_eq!(left, right)直接比较两个值是否相等失败时会同时打印左右两边的实际值调试定位更快——这是它比assert!(a b)更常用的原因。solutions/17_tests/tests2.rs 给出的四条用例是#[test] fn you_can_assert_eq() { assert_eq!(power_of_2(0), 1); assert_eq!(power_of_2(1), 2); assert_eq!(power_of_2(2), 4); assert_eq!(power_of_2(3), 8); }从这组用例可以读出两点设计意图从左到右、逐位翻倍1, 2, 4, 8正好是1 0到1 3的期望值覆盖了指数的最低几位任何位运算实现错误如误写成n 1都会立刻暴露类型转换是隐式正确的1 n中1会按返回类型推断为u64n: u8决定位移量。assert_eq!要求两侧类型一致都实现PartialEq且类型相同这里u64 u64成立若手误写成assert_eq!(power_of_2(0), 1u32)会直接得到编译错误而非测试失败——类型系统在这里替你兜底。四、tests3用 #[should_panic] 测试“会 panic 的代码路径”exercises/17_tests/tests3.rs 是三道题中最完整的案例被测对象是一个在非法输入下主动panic!的构造函数struct Rectangle { width: i32, height: i32, } impl Rectangle { // Dont change this function. fn new(width: i32, height: i32) - Self { if width 0 || height 0 { // Returning a Result would be better here. But we want to learn // how to test functions that can panic. panic!(Rectangle width and height must be positive); } Rectangle { width, height } } }注释特意点明现实中返回Result是更好的设计这一点与后续13_error_handling练习的主题呼应但此处刻意保留 panic 版本目的就是教你测试会 panic 的函数。练习包含三个测试逐一说明1. 正常路径字段值断言#[test] fn correct_width_and_height() { let rect Rectangle::new(10, 20); assert_eq!(todo!(), 10); // Check width assert_eq!(todo!(), 20); // Check height }todo!()是一个宏它让程序直接 panic占位提示“此处尚未实现”。参考解把它替换为结构体字段访问let rect Rectangle::new(10, 20); assert_eq!(rect.width, 10); // Check width assert_eq!(rect.height, 20); // Check height注意这里验证的是构造参数确实被原样存进了字段——这是对Rectangle { width, height }这种字段缩写构造的回归测试。另外一个仓库级细节dev/Cargo.toml 的 clippy lint 配置里写着todo forbid注释是 “You forgot atodo!()!”也就是说忘记把todo!()替换掉会直接触发 lint 错误练习从机制上强迫你补全断言。2. 预期 panic 路径#[should_panic]#[test] fn negative_width() { let _rect Rectangle::new(-10, 10); } #[test] fn negative_height() { let _rect Rectangle::new(10, -10); }这两个测试的注释都要求“检查负宽/负高时程序是否 panic”。但原样保留它们测试一定会失败Rectangle::new(-10, 10)触发panic!而 libtest 默认期望测试函数正常返回panic 即判失败。修复方式见 solutions/17_tests/tests3.rs是在函数上追加属性#[test] #[should_panic] // Added this attribute to check that the test panics. fn negative_width() { let _rect Rectangle::new(-10, 10); } #[test] #[should_panic] // Added this attribute to check that the test panics. fn negative_height() { let _rect Rectangle::new(10, -10); }#[should_panic]反转了通过标准测试函数 panic 才算通过正常返回反而算失败。这样就把“非法输入必须被拒绝”这一行为契约固化成了可执行断言。两个测试分别覆盖宽度为负、高度为负两个分支与new中的width 0 || height 0条件一一对应。顺带一提dev/Cargo.toml 对练习包配置了[profile.dev]与[profile.release]下的panic abort这属于 Rustlings 对练习运行环境的统一约束同文件还通过unsafe_code forbid、unstable_features forbid等 lint 约束练习代码风格本练习关注的核心仍是#[should_panic]语义本身为“预期崩溃”的代码路径编写可验证的测试。五、动手验证如何确认自己真的做对了完成修改后有三层证据链可以自我验证单独跑该练习的测试。Rustlings 把每道练习注册为独立 bin target见 dev/Cargo.toml而 Rustlings 运行器内部就是对该练习执行cargo test并检查退出码src/exercise.rs。你也可以在练习包环境中直接以cargo test tests1/cargo test tests2/cargo test tests3的方式单独筛选运行观察输出中test result: ok与用例数tests1/2 各 1 个、tests3 共 3 个观察失败信息。把assert_eq!(power_of_2(2), 4)故意改成3cargo test会打印左右值不匹配的详细信息把#[should_panic]删掉再跑negative_width会因 panic 而失败——这两类失败信息正是断言机制存在的意义Rustlings 的自检验证了这套判定。仓库的集成测试目录 tests/test_exercises/ 内置了test_success.rs、test_failure.rs、compilation_success.rs、compilation_failure.rs四类最小样例见 tests/test_exercises/exercises/配合 tests/integration_tests.rs 验证“测试通过/失败、编译通过/失败”四种状态都能被运行器正确识别——也就是说你在这三道练习里看到的“通过”信号与 Rustlings 自身 CI 验证过的判定逻辑是同一套。六、小结三道题背后的测试心智模型assert!(cond)断言布尔条件要断言“为假”时用!取反tests1 的!is_even(-1)。assert_eq!(a, b)断言两值相等失败时打印双方实际值且要求类型一致tests2 的四条 2 的幂用例。#[should_panic]为“非法输入必须 panic”的行为契约编写测试反转通过标准tests3 的负宽/负高用例。配套工程事实测试代码用#[cfg(test)]条件编译隔离、用use super::*引入被测项Rustlings 以cargo test的退出码判定练习成败并以 clippytodo forbid杜绝占位符残留。掌握这三点之后后续练习中“make the tests pass”的要求就不再是谜先看清#[test]函数里的断言在验证什么行为再修改被测代码或补全断言使cargo test全绿即可。【免费下载链接】rustlings:crab: Small exercises to get you used to reading and writing Rust code!项目地址: https://gitcode.com/gh_mirrors/ru/rustlings创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考