这次要来谈的是Rust的测试框架,并且重新调整目录架构。
撰写测试
Rust本身就自带测试框架,无须安装额外library,这边直接把上次所写的main改写成test case如下:
fn main() { let mut core: RVCore = Default::default(); core.run(5);}#[test]fn test_core_run() { // Copy from main let mut core: RVCore = Default::default(); core.run(5);}
可以看到在定义fn的前面多了一个#[test] 属性,代表这是一个test case 的定义,凡是带有test属性的function就会自动被当作测试执行。
cargo test
就可以看到测试结果:
#[test]running 1 testtest test_core_run ... oktest result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
只是把core跑起来没有用处,测试需要断言(assertion)来判断程式的正确性:
#[test]fn test_core_run() { let mut core: RVCore = Default::default(); assert_eq!(0, core.pc); core.run(5); assert_eq!(20, core.pc);}
assert_eq是Rust内建的macro,很多测试框架的convention都是把预期值放左边,实际值放右边,这里也遵循此规则。初始化之后我们预期core的PC应该为0,跑了5个指令后,假设每个指令长度为4,PC就会变成5*4 = 20。重新执行cargo test
,一样可以看到test pass的输出。
为了观察test fail会发生什么事,先把最后一行改成如下:
assert_eq!(10, core.pc);
执行cargo test
就会看到test fail的讯息,以及是哪个test case 的哪一行fail:
thread 'test_core_run' panicked at 'assertion failed: `(left == right)` left: `10`, right: `20`', src/main.rs:33:5note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
在test case数量很多时,这些都是很有用的资讯,可以帮助我们快速定位出错的地方。
调整目录结构
到目前为止所有程式码都写在同一个档案,虽然易于管理,当程式码逐渐增加,可读性很容易随之降低。这边将RVCore的实作独立成为module:
建立src/rv_core.rs,将RVCore的定义以及test code全部搬过去修改RVCore可见度,所有会被外部使用的成员都要加上pub修饰词,目前有2个地方需要修改pub struct RVCore // RVCore需要被外部的main.rs使用,必须为publicpub fn run() // main.rs会呼叫此method修改test code,用tests module包起来并且加上cfg(test)属性,以避免cargo run
编译test code,以加速编译。修改结果如下:#[cfg(test)]mod tests { use super::*; // 使RVCore所有成员在此module中可见 #[test] fn test_core_run() { let mut core: RVCore = Default::default(); assert_eq!(0, core.pc); core.run(5); assert_eq!(20, core.pc); }}
修改main.rs,加上适当的prefix在一开始加上mod rv_core
,宣告rv_core模组所有的RVCore都要改成rv_core::RVCore,代表我想使用的是rv_core模组里面的RVCore依照Rust官方的教学,unit tests一般放在src/底下,目前直接与待测程式码放在同档案,以方便test code存取待测物。教学也有提到可以在src/旁边新增一个tests/资料夹来放test code,不过那是属于integration test的範畴,之后有需要再来研究。
完整的repo可以参考此连结