【Rust】がばがばRust独学 - 11. Tests - 1 How to Write Tests
Rustの公式ドキュメントを通して、Rustを独学していく記事になります。(がば要素あり)
今回は、Testについて学びつつ、気になった点をコード確認した結果を記事にしています。
- versions
$ rustc --version rustc 1.40.0 (73528e339 2019-12-16)
How to Write Tests
公式ドキュメント(How to Write Tests - The Rust Programming Language)によると、テストは3つのアクションを持っているそうです。
- Set up any needed data or state.
- Run the code you want to test.
- Assert the results are what you expect.
必要なデータ・状態を設定し、テストコードを実行し、結果が期待通りで有ることをアサートします。
テスト関数の構造
テスト関数を実施するために、とりあえず新しいライブラリプロジェクトを作成しました。
$ cargo new demo-test --lib $ demo-test
テストコードについては、src/lib.rs
(training-rust/lib.rs at master · KentaHara/training-rust · GitHub)を参照ください。
まず、初期で記載されているテストを実行すると、正常動作が確認できました。
#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } }
running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
別名関数を作成して実行すると、各々 tests::xxxxx
としてチェックされていることが確認できました。
#[test] fn exploration() { assert_eq!(2 + 2, 4); }
running 2 tests test tests::exploration ... ok test tests::it_works ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
panicを起こすと FAILED
となり、異常検知が出来ていることが確認できます。
#[test] fn another() { panic!("Make this test fail"); }
running 3 tests test tests::exploration ... ok test tests::it_works ... ok test tests::another ... FAILED failures: ---- tests::another stdout ---- thread 'tests::another' panicked at 'Make this test fail', src/lib.rs:24:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::another test result: FAILED. 2 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
assert!
マクロ利用によるテスト結果確認
デモコード(training-rust/lib.rs at master · KentaHara/training-rust · GitHub)のように、 Rectangle
の構造体および can_hold
関数を定義して、 assert!
マクロによるチェックを実施しました。 assert!
マクロは bool
によるチェックになります。
#[derive(Debug)] struct Rectangle { width: u32, height: u32, } impl Rectangle { fn can_hold(&self, other: &Rectangle) -> bool { self.width > other.width && self.height > other.height } } // ... #[cfg(test)] mod tests { use super::*; // ... #[test] fn larger_can_hold_smaller() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(larger.can_hold(&smaller)); } #[test] fn smaller_cannot_hold_larger() { let larger = Rectangle { width: 8, height: 7, }; let smaller = Rectangle { width: 5, height: 1, }; assert!(!smaller.can_hold(&larger)); } }
running 5 tests ... test tests::larger_can_hold_smaller ... ok test tests::smaller_cannot_hold_larger ... ok
assert_eq!
、 assert_ne!
によるテスト
it_adds_two
では 4 == 4
、it_adds_two_negative
では 5 == 4
としてテストを実施しました。
pub fn add_two(a: i32) -> i32 { a + 2 } // ... #[cfg(test)] mod tests { use super::*; // ... #[test] fn it_adds_two() { assert_eq!(4, add_two(2)); } #[test] fn it_adds_two_negative() { assert_ne!(5, add_two(2)); } }
running 7 tests ... test tests::larger_can_hold_smaller ... ok test tests::smaller_cannot_hold_larger ... ok
テスト失敗時のメッセージ変更
assert!
の第2引数と第3引数に対してフォーマットの利用が可能です。これは便利...
#[test] fn change_error_message() { let result = String::from("Hello Rust!"); assert!( result.contains("Hello New World!"), "result did not contain name, value was `{}`", result ); } #[test] fn change_error_message_use_eq() { let result = String::from("Hello Rust!"); assert_eq!( result, String::from("Hello New World!"), "result did not contain name, value was `{}`", result ); } #[test] fn change_error_message_use_ne() { let result = String::from("Hello Rust!"); assert_ne!( result, String::from("Hello Rust!"), "result did not contain name, value was `{}`", result ); }
running 8 tests ... test tests::change_error_message ... FAILED ... failures: ---- tests::change_error_message stdout ---- thread 'tests::change_error_message' panicked at 'result did not contain name, value was `Hello Rust!`', src/lib.rs:98:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace. failures: tests::change_error_message test result: FAILED. 7 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
panicのチェック
シンプルにパニックを起こす関数 simple_panic
を定義して確認すると、 should_panic
である場合はpanicが見つけられなければ FAILED
となる。
pub fn simple_panic(flg: bool) -> bool { if flg { panic!("Panic!!"); } true } #[cfg(test)] mod tests { use super::*; // ... #[test] #[should_panic] fn should_panic_test_panic() { simple_panic(true); } #[test] #[should_panic] fn should_panic_test_nopanic() { simple_panic(false); } }
running 12 tests ... test tests::should_panic_test_nopanic ... FAILED test tests::should_panic_test_panic ... ok failures: ... ---- tests::should_panic_test_nopanic stdout ---- note: test did not panic as expected failures: tests::change_error_message tests::change_error_message_use_eq tests::change_error_message_use_ne tests::should_panic_test_nopanic test result: FAILED. 8 passed; 4 failed; 0 ignored; 0 measured; 0 filtered out
上記の場合は、 note: test did not panic as expected
として注意書きがされる。
Result<T,E>
の利用
Resultを返り値として返すこともでき、Errの場合にはエラーメッセージを出すことも可能です。
#[test] fn it_works_use_result_ok() -> Result<(), String> { Ok(()) } #[test] fn it_works_use_result_err() -> Result<(), String> { Err(String::from("ERROR MESSAGE")) }
---- tests::it_works_use_result_err stdout ---- Error: "ERROR MESSAGE" thread 'tests::it_works_use_result_err' panicked at 'assertion failed: `(left == right)` left: `1`, right: `0`: the test returned a termination value with a non-zero status code (1) which indicates a failure', src/libtest/lib.rs:196:5
まとめ
mod
には#[cfg(test)]
を付与してtestということをconfigとして設定- テスト用の関数には
#[test]
を利用- 必ずpanicになるか否かを確認するためには
#[should_panic]
を利用
- 必ずpanicになるか否かを確認するためには
- 各種マクロによりテスト結果への対応可能
assert_eq!(a, b)
:a == b
assert_en!(a, b)
:a != b
assert!(x)
:x == true
- 各種マクロの引数を利用してテスト失敗時のメッセージを変更可能
Result<T,E>
による成否のチェックも可能
最後に
シンプルなテストの記述方法で書きやすそうですね。 Result<T,E>
を使える点がとてもありがたいです。
次回も引き続き、Testsに関して進めていきます。