Rust 怎么写测试
先跑起来
不说什么具体的知识,我们先一步步的,来写个最简单的测试,并且让他啊跑起来,看看
rust 下的测试是什么样子的: 1. 创建个 lib
工程:cargo new simplest-test --lib
2. 在
src/lib.rs
里面,rust 已经自动帮我们写下了如下代码:
1
2
3
4
5
6
7
mod test {
fn it_works() {
assert_eq!(2 + 2, 4);
}
}cargo test
1
2
3
4
5
6
7
8
9
10
11
12
13
14 Compiling simplest-test v0.1.0 (/User/xxx/simplest-test)
Finished dev [unoptimized + debuginfo] target(s) in 5.91s
Running target/debug/deps/simplest_test-c430fbaec5f55b85
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests simplest-test
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
经过上述三步,我们已经创建了一个最简单的测试工程,并且运行了自带的测试。
在代码层面,测试本身无需多说,结构上我们看到,与业务代码不同,测试在
module 上增加了 attribute: #[cfg(test)]
,在测试方法上增加了
attribute: #[test]
。 -
#[cfg(test)]
:配置编译条件,只有在 test
模式下,被标记的代码块才会被编译(换句话说,它确保 release
中不包含测试代码) -
#[test]
:被标记的方法将被视为测试来执行
在 output 中,还包含了两部分“running x test”,第一部分是我们已有的测试,第二部分为文档测试,本文暂不涉及。
断言
rust 原生提供了几种简单的测试断言,能够满足基本的测试需求,以下是 rust 的测试断言与 junit 测试断言的对应表:
rust | junit |
---|---|
assert!() | assertTrue() |
assert_eq!() | assertEqual() |
assert_ne!() | assertNotEqual() |
#[should_panic(expected = "{error message}")] | assertThrows() |
对 panic 的断言使用的是 attribute 而不是 macro
单元测试
rust 的封装性与 java 略有不同,只有 default(private) 与 pub(public),那么有个问题:我想写个单元测试,难道必须要把需要测试的函数都 pub 出来吗?(其实 java 里面也有要不要测试 private 方法的讨论,参见《修改代码的艺术》)
而在 rust 中,我们不用过多担心测试私有函数的问题,因为通常 rust
的单元测试会直接与被测代码放置在一起,见以下代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14// src/biz.rs
fn int_adder(op1:i32, op2:i32) -> i32 {
op1 + op2
}
mod tests {
use super::*;
fn should_return_4_when_2_add_2() {
assert_eq!(int_adder(2, 2), 4);
}
}tests
,child
module 能够合法的访问他的 parent,因此能够直接调用其 parent
的私有方法。
同时,#[cfg(test)]
确保了该单元测试不会被编译(除非在
test 模式下)。
集成测试
与 java 类似,rust 也能识别特定的测试目录 tests, tests 目录与 src 目录并列,作为专门存放集成测试的地方。
显然,放在 tests
中的测试代码不再能访问到私有函数了。不过集成测试本来也不应该测试细节。另外,放置在
tests 目录下的测试代码,不再需要#[cfg(test)]
attribute
了,rust 编译器会自动识别所有 tests 目录下的代码为测试。
对上一节中的例子进行修改,得到如下集成测试: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21-----------------------------------------------
// src/biz.rs
-----------------------------------------------
pub fn int_adder(op1:i32, op2:i32) -> i32 {
op1 + op2
}
-----------------------------------------------
// src/lib.rs
-----------------------------------------------
pub mod biz;
-----------------------------------------------
// tests/biz_test.rs
-----------------------------------------------
use simplest_test::biz;
fn should_return_4_when_2_add_2() {
assert_eq!(biz::int_adder(2, 2), 4);
}
再看看测试输出: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 Compiling simplest-test v0.1.0 (/Users/xxx/simplest-test)
Finished dev [unoptimized + debuginfo] target(s) in 0.55s
Running target/debug/deps/simplest_test-c430fbaec5f55b85
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Running target/debug/deps/biz_test-c9cf61d397408434
running 1 test
test should_return_4_when_2_add_2 ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Doc-tests simplest-test
running 0 testsrunning x tests
,比之前多的一部分,就是我们新增的集成测试了。tests
目录下的测试会单独占据一块输出。
测试运行控制
- 默认情况下,rust
采用多线程并行执行所有测试,当有串行需要时可以执行:
cargo test -- --test-threads={thread_numbers}
来控制执行测试的线程数。 - rust 默认不打印 passed test
的任何输出,当有需要打印输出时,执行:
cargo test -- --show-output
- 当期望只运行某个特定测试时,执行:
cargo test {test_function_name}
- 当期望只运行某一类测试时,执行:
cargo test {test_function_name_matcher}
- 与 junit 类似,当期望 ignore 测试时,在测试函数上添加:
#[ignore]
attribute