アサーション
いわゆる assert。
$ cargo new test_example1 --bin
$ tree
| Cargo.toml
|
\---src
main.rs
fn main() {
let x = 1;
let y = 2;
debug_assert!(x <= y, "{} > {}", x, y);
debug_assert_eq!(x, y);
assert!(x <= y, "{} != {}", x, y);
assert_eq!(x, y);
}
$ cd test_example1
$ cargo run
thread '<main>' panicked at 'assertion failed: `(left == right)` (left: `1`, right: `2`)', src\main.rs:6
An unknown error occurred
$ cargo run --release
thread '<main>' panicked at 'assertion failed: `(left == right)` (left: `1`, right: `2`)', src\main.rs:9
An unknown error occurred
assert! マクロ
第一引数が条件で、第二数以降はアサーション時のメッセージ表示用、println! と同じように出力フォーマットを指定できる。
条件が false だと、指定したメッセージを panic! (回復不能なエラーとして、指定されたメッセージの出力とともにプログラムを終了する) に渡す。
assert_eq! マクロ
同値条件専用の assert!。
assert_eq!(a, b)
は
assert!(a == b, "assertion failed: `(left == right)` (left: `{:?}`, right: `{:?}`)", a, b);
と記述しているのとおなじ。
debug_assert! マクロ
リリースビルド時に無効化される assert!。
debug_assert_eq! マクロ
リリースビルド時に無効化される assert_eq!。
テスト
#[test]
関数に test 属性を指定すると、テストとして認識される。
$ cargo new test_example2
$ tree
| Cargo.toml
|
\---src
lib.rs
# [test] // test 属性
fn it_works() {
assert!(false, "always false"); // 必ず失敗する
}
# [test] // test 属性
fn it_works2() {
assert!(true, "always true"); // 必ず成功する
}
test 属性のついた関数の中で panic! が起こると、テストに失敗したものとして扱われる。
$ cargo test
running 2 tests
test it_works2 ... ok
test it_works ... FAILED
failures:
---- it_works stdout ----
thread 'it_works' panicked at 'always false', src\lib.rs:3
failures:
it_works
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured
cargo test で test 属性のついた関数が実行される。
#[should_panic]
should_panic 属性を付けると、必ず失敗することをテストできる。
$ cargo new test_example3
$ tree
| Cargo.toml
|
\---src
lib.rs
# [test]
# [should_panic(expected = "always false")] // should_panic 属性
fn it_works() {
assert!(false, "always false"); // ここのメッセージを expected に指定する
}
# [test]
fn it_works2() {
assert!(true, "always true");
}
意図しない原因で panic! が発生してしまっていては、ただしくテストできているとは言えない。
そこで should_panic に expected を指定している。ここに指定してある文字列が panic! の出力に含まれていれば、ただしく失敗していると判断している。
$ cargo test
running 2 tests
test it_works2 ... ok
test it_works ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
Doc-tests test_example3
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured
通常のテストのあとに Doc-tests というものも走っているけど、これについては後述する。
#[ignore]
常にすべてのテストを実行すると時間がかかるので、省略可能なテストを ignore 属性で指定できる。
$ cargo new test_example4
$ tree
| Cargo.toml
|
\---src
lib.rs
# [test]
# [should_panic(expected = "always false")]
fn it_works() {
assert!(false, "always false");
}
# [test]
# [ignore] // ignore 属性
fn it_works2() {
assert!(true, "always true");
}
$ cargo test
running 2 tests
test it_works2 ... ignored
test it_works ... ok
test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured
$ cargo test -- --ignored
running 1 test
test it_works2 ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
cargo test に -- がついているのはミスではなくて、--ignore が cargo にではなく実行ファイルへの引数のため。
テストモジュール
テストのときだけビルドされるモジュールを作る。
$ cargo new test_example5
$ tree
| Cargo.toml
|
\---src
lib.rs
pub fn square(x: i32) -> i32 {
x * x
}
# [cfg(test)]
mod tests {
use super::*; // 外の定義にアクセスするため use
// ふつうにテストを書く
#[test]
fn it_works() {
assert_eq!(square(2), 4);
}
}
#[cfg(test)] で test のときだけビルドされる (cfg でコンパイル時オプションを指定できる)。
$ cargo test
running 1 test
test tests::it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
これは、ファイル単位の単体テストを記述するときの書き方。
tests ディレクトリ
tests ディレクトリを作ると、結合テストを書ける。
$ cargo new test_example6
$ cd test_example6
$ mkdir tests
$ tree
| Cargo.toml
|
+---src
| lib.rs
\---tests
lib.rs
pub fn square(x: i32) -> i32 {
x * x
}
extern crate test_example6; // tests は別の crate になっているので extern が必要
use test_example6::*; // test_example6:: を省略するため use
# [test]
fn it_works() {
assert_eq!(square(2), 4);
}
$ cargo test
running 1 test
test it_works ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
ドキュメンテーションテスト
Rust は、//! で開始するコメントでモジュールの説明を、/// で開始するコメントで関数の説明を、Markdown 形式で記述できる。
ドキュメンテーションテストで、これらのコメントの中に含まれるコードが、ただしく動作するかテストできる。
$ cargo new test_example5
$ tree
| Cargo.toml
|
\---src
lib.rs
//! This module provides a square function.
//!
//! ```
//! use test_example7::*;
//!
//! assert_eq!(square(3), 9);
//! ```
/// This function return a square of x.
///
/// ```
/// use test_example7::*;
///
/// assert_eq!(square(2), 4);
/// ```
pub fn square(x: i32) -> i32 {
x * x
}
$ cargo test
Doc-tests test_example7
running 2 tests
test square_0 ... ok
test _0 ... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured
test square_0 は関数のコメント部分のコードに対するテストで、test _0 はモジュールのコメント部分のコードに対するテスト。
ポストフィックスの _0 は連番で、複数のコードを書くと増えていく。
ちなみに
$ cargo doc
でドキュメントを生成できる。
まとめ
- 単体テストは
#[cfg(test)]を指定してファイル単位で記述する。 - 統合テストは
testsディレクトリを作って、その中に記述する。 - ドキュメント書きに、ドキュメンテーションテストを使おう。