趣味学RUST基础篇(测试)
代码测试是啥?
如果把程序比作一个生命体,就可以把代码测试想象成给程序做“体检”。我们写程序的时候,当然希望它按照我们想的去工作,但这个保证起来其实挺难的。
Rust语言本身已经帮我们避免了很多常见错误(比如用错数据类型或者内存管理出问题),但它也没法知道我们写的逻辑对不对。比如说,我们写了个给数字加2的函数,Rust能确保这个函数只能接收数字,但它没法知道这个函数是不是真的加了2,而不是误写成加10或减50。
这时候测试就派上用场了!我们可以专门写一些测试代码,来验证我们的主代码是否正常工作。比如针对那个加2的函数,我们可以写个测试:“如果我输入3,结果应该是5”。以后每次改代码,都可以跑一下这些测试,确保没有不小心把原来对的功能改错了。
测试本身是门大学问,这章我们不会面面俱到,主要看看Rust提供的测试工具怎么用:包括写测试的语法、运行测试的方法,以及怎么合理组织测试代码。
当然可以!下面是对 Rust 书籍第十一章第一节 “编写测试” 的趣味化、通俗易懂的转写版本,保留原意的同时,让它更像朋友之间的聊天,轻松易懂,适合初学者阅读。
Rust 测试入门:给你的代码做个体检!
想象一下,你写了一个函数,它负责把两个数加起来。你信心满满地运行程序,结果用户输入 2 + 2
,系统却返回了 5
……
这不就完蛋了吗?
为了避免这种“数学老师当场去世”的尴尬场面,我们就要给代码做“体检”——这就是**测试(Testing)**的作用。
在 Rust 中,测试就像是一位尽职尽责的质检员,专门检查你写的代码有没有“生病”。它通常会做三件事:
- 准备道具:比如先造两个数字、一个对象。
- 执行代码:让被测试的函数跑一跑。
- 检查结果:看看输出是不是你想要的,不是就“报警”!
Rust 给我们准备了一套“测试工具箱”,里面有:
#[test]
:标记哪个函数是“质检员”。assert!
、assert_eq!
:断言工具,相当于“必须满足这个条件”。should_panic!
:专门检查“该崩溃的时候是不是真崩溃了”。
下面我们一个个来认识这些“工具”。
第一步:让函数变成“测试员”
怎么让一个普通函数变成“测试函数”?很简单,加一行注解:
#[test]
fn it_works() {assert_eq!(2 + 2, 4);
}
只要加上 #[test]
,Rust 就知道:“哦,这个函数是用来测试的,等会儿要运行它。”
你用 cargo new my_crate --lib
创建项目时,Rust 会自动给你生成一个测试模板:
#[cfg(test)]
mod tests {#[test]fn it_works() {let result = 2 + 2;assert_eq!(result, 4);}
}
别被 #[cfg(test)]
和 mod tests
吓到,它们的意思是:
mod tests
:建一个叫“tests”的小房间,专门放测试代码。#[cfg(test)]
:这个房间只在运行测试时才存在,发布代码时自动消失(省空间)。
跑测试:cargo test
在终端输入:
$ cargo test
你会看到类似这样的输出:
running 1 test
test tests::it_works ... oktest result: ok. 1 passed; 0 failed
恭喜!你的“2+2=4”测试通过了!
如果测试失败了呢?比如你故意写成:
#[test]
fn another() {panic!("我就是要失败!");
}
再运行 cargo test
,就会看到:
test tests::another ... FAILED
thread 'tests::another' panicked at '我就是要失败!'
Rust 会清楚告诉你:哪个测试失败了,为什么失败,甚至在第几行出的问题。简直比老师批作业还仔细!
断言:用 assert!
确保条件成立
有时候我们不想比数字,而是想检查某个条件是否成立。比如:
“这个矩形能装下另一个矩形吗?”
我们可以写个 can_hold
方法:
struct Rectangle {width: u32,height: u32,
}impl Rectangle {fn can_hold(&self, other: &Rectangle) -> bool {self.width > other.width && self.height > other.height}
}
然后写个测试:
#[test]
fn larger_can_hold_smaller() {let big = Rectangle { width: 8, height: 7 };let small = Rectangle { width: 5, height: 1 };assert!(big.can_hold(&small)); // 断言:大的能装下小的
}
assert!(条件)
:如果条件为true
,测试通过;为false
,测试失败并 panic。- 如果我们把
>
错写成<
,测试就会失败,Rust 会提醒你:“兄弟,你代码有 bug!”
比较相等?用 assert_eq!
和 assert_ne!
最常用的测试方式就是:看看函数输出是不是你预期的值。
比如你写了个 add_two
函数:
pub fn add_two(a: i32) -> i32 {a + 2
}
测试它:
#[test]
fn it_adds_two() {assert_eq!(4, add_two(2)); // 期望:add_two(2) == 4
}
assert_eq!(期望值, 实际值)
:两个值相等就通过。assert_ne!(a, b)
:两个值不相等才通过(比如你希望输出不是 0)。
如果测试失败,Rust 会贴心地告诉你:
assertion `left == right` failedleft: 4right: 5
这比只说“断言失败”有用多了,一眼就知道是 4 != 5
,bug 很好定位。
提示:自定义结构体要实现
#[derive(PartialEq, Debug)]
才能用assert_eq!
。
给错误加点“人情味”:自定义错误消息
默认的错误信息有时候太冷冰冰了。比如:
assert!(result.contains("Carol"));
失败时只说:“断言失败”,你根本不知道 result
到底是啥。
解决办法:加自定义消息!
assert!(result.contains("Carol"),"Greeting did not contain name, value was `{}`",result
);
这样失败时就会输出:
Greeting did not contain name, value was `Hello!`
是不是清楚多了?就像朋友在告诉你:“兄弟,你忘了把名字加进去啊!”
测试“崩溃”?用 should_panic
有些代码,我们希望它在出错时崩溃。比如一个“猜数字”游戏,只允许 1~100 的数字:
pub struct Guess {value: i32,
}impl Guess {pub fn new(value: i32) -> Guess {if value < 1 || value > 100 {panic!("必须是 1~100 的数,你给的是 {}!", value);}Guess { value }}
}
我们想测试:如果输入 200,它会不会 panic?
可以这样写:
#[test]
#[should_panic]
fn too_big() {Guess::new(200);
}
#[should_panic]
:表示“这个测试,只有代码 panic 了才算成功!”- 如果
Guess::new(200)
没有 panic,测试反而失败。
更进一步,你还可以检查 panic 的错误信息是否包含某个关键词:
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn too_big() {Guess::new(200);
}
这样就确保了 panic 的原因是对的,而不是因为别的 bug 导致的崩溃。
更优雅的失败方式:用 Result<T, E>
前面的测试,失败就直接 panic
,有点“暴力”。
其实你也可以让测试返回 Result
,更温和地处理错误:
#[test]
fn it_works() -> Result<(), String> {if 2 + 2 == 4 {Ok(())} else {Err("2 + 2 居然不等于 4!".to_string())}
}
好处是:你可以在测试里用 ?
操作符,处理可能出错的操作。
注意:用了
Result
就不能再用#[should_panic]
了。想检查是否返回Err
,可以用assert!(result.is_err())
。
总结:测试的“黄金三原则”
- 准备 → 执行 → 断言:测试三步走,稳如老狗。
- 多用
assert_eq!
和assert_ne!
:清晰明了,失败时还能看到具体值。 - 善用自定义消息和
should_panic
:让错误信息更有“人情味”,定位 bug 更快。