這次要來談的是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 test
test test_core_run ... ok
test 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:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
在test case數量很多時,這些都是很有用的資訊,可以幫助我們快速定位出錯的地方。
到目前為止所有程式碼都寫在同一個檔案,雖然易於管理,當程式碼逐漸增加,可讀性很容易隨之降低。這邊將RVCore的實作獨立成為module:
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);
}
}
mod rv_core
,宣告rv_core模組依照Rust官方的教學,unit tests一般放在src/底下,目前直接與待測程式碼放在同檔案,以方便test code存取待測物。教學也有提到可以在src/旁邊新增一個tests/資料夾來放test code,不過那是屬於integration test的範疇,之後有需要再來研究。
完整的repo可以參考此連結