一、测试
软件测试越来越受到所有开发人员的重视,当然,很多人也不重视。只是相对于以前来说。至少,现在技术型公司都安排了测试部门,以前可都是开发人员自己测试一下就OK了。Edsger W. Dijkstra 说“Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence.”可见软件测试多么重要又多么让人绝望。其实这就是投入和收益的比例,当二者的比例急剧缩小时,就可以考虑不再投入了。
软件测试,对Rust来说,是必须支持而不是一个可选项,一门新的语言不支持测试,那么基本可以理解要挂了(这里指的是自身语言级测试)。对于Rust来说,其实仍然是使用以前的属性来指示测试的进行,所以说,语言的学习是一个环环相扣的过程,每一个细节都要认真把握,前后贯通才能把一门语言整体上搞清楚,真正的做到学以致用,游刃有余。
二、rust中的测试
rust中提供了对三种测试的支持:
1、单元测试:对应着函数的级别
2、文档和模块测试:对应着模块和说明文档级别
3、集成测试:对应着工程级别
在实际的开发中,既存在有手动测试也有自动测试,手动测试一般是对于简单少量,或者定位某个具体的问题时,更有针对性。而一般来说,单元测试都会用到自动测试,所以要学会在rust中编写自动化测试代码。
三、实际例程
1、函数级别
先看一个最简单的应用,就明白怎么回事:
#[test]
fn mytasks() {
assert_eq!(3, 4);
}
#[test]
fn second() {
panic!("fail");
}
在编译的过程中,如果看到这个test属性,cargo build编译器就会自动略过这部分代码,而如果使用cargo test就可以运行这些代码,这就支持的很好了。rust提供了assert!(expr) 和assert_eq!(expr, expr)、assert_ne!等宏断言。有过其它语言中相关的经验的一眼就明白怎么回事儿了。再看一个例子:
#[test]
#[should_panic]
fn mytest() {
assert!(false);
}
这个属性的意思是如果你想略过某段代码测试中的panic!(比如未完成或者故意宕机等),同样如果想忽略一段代码可以用下面的方法属性:
#[test]
#[ignore]
fn igtest() {
// code
}
2、文档和模块级别的测试
#[cfg(test)]标识这是一个模块级别的测试,其实它就是一组函数级别的测试。
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::add_two;
#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
}
再看一个文档级别测试:
//! The `adder` crate provides functions that add numbers to other numbers.
//!
//! # Examples
//!
//! ```
//! assert_eq!(4, adder::add_two(2));
//! ```
/// This function adds two to its argument.
///
/// # Examples
///
/// ```
/// use adder::add_two;
///
/// assert_eq!(4, add_two(2));
/// ```
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
assert_eq!(4, add_two(2));
}
}
rust把代码和代码注释都看作开发文档,它可以自动抽取代码中的文档形成标准的文档集合,还可以对示例代码进行测试。
3、工程级别的测试
集成测试就是有一个专门的tests文件夹,在其中包含相关的测试代码文件。假设在工程文档中提供了下面的代码:
// 这个 crate名字为mybase,在集成测试中用 extern 说明。
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
在tests文件夹中生成一个测试文件mycall.rs:
// 声明被测试的外部 crate,就像其他使用该 crate 的程序要声明的那样。
extern crate mybase;
#[test]
fn test_add() {
assert_eq!(mybase::add(6, 2), 5);
}
测试的结果是:
C:/Users/dell/.cargo/bin/cargo.exe test --color=always --package mybase --test mycall "" --no-fail-fast -- -Z unstable-options --format=json --show-output
Finished test [unoptimized + debuginfo] target(s) in 0.03s
Running target\debug\deps\mycall-bf0278af00c7eb58.exe
Left: 8
Right: 5
<Click to see difference>
thread 'test_add' panicked at 'assertion failed: `(left == right)`
left: `8`,
right: `5`', tests\mycall.rs:5:5
stack backtrace:
0: std::panicking::begin_panic_handler
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4\/library\std\src\panicking.rs:483
1: std::panicking::begin_panic_fmt
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4\/library\std\src\panicking.rs:437
2: mycall::test_add
at .\tests\mycall.rs:5
3: mycall::test_add::{{closure}}
at .\tests\mycall.rs:4
4: core::ops::function::FnOnce::call_once<closure-0,tuple<>>
at C:\Users\dell\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib\rustlib\src\rust\library\core\src\ops\function.rs:227
5: core::ops::function::FnOnce::call_once
at /rustc/7eac88abb2e57e752f3302f02be5f3ce3d7adfb4\library\core\src\ops\function.rs:227
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
error: test failed, to rerun pass '-p mybase --test mycall'
Process finished with exit code 101
通过了就运行直接成功,没啥可说的,没通过的话会有上述的说明,并告知原因。
测试代码看起来没有什么难度,但重要是要考虑周全,要用不同于开发的思路和方式去写测试代码。
三、总结
单元测试是程序员必须搞定的,至于其它测试,可以委托给测试人员,当然也可以自己测试。测试的重要使得开发人员必须重视越来,TDD就是从测试的角度来进行开发,从而更好的控制整个程序的安全性和健壮性。思维的改变到思想的提炼和形成,才是一个程序员最宝贵的财富,而不是掌握了几门语言,几个技巧。共勉!
努力吧,归来的少年!