アサーション
いわゆる 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
ディレクトリを作って、その中に記述する。 - ドキュメント書きに、ドキュメンテーションテストを使おう。