Rust のテストフレームワーク

More than 3 years have passed since last update.


アサーション

いわゆる assert


新規作成

$ cargo new test_example1 --bin



ファイル構成

$ tree

| Cargo.toml
|
\---src
main.rs


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


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 testtest 属性のついた関数が実行される。


#[should_panic]

should_panic 属性を付けると、必ず失敗することをテストできる。


新規作成

$ cargo new test_example3



ファイル構成

$ tree

| Cargo.toml
|
\---src
lib.rs


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_panicexpected を指定している。ここに指定してある文字列が 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


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



テスト(ignored)

$ cargo test -- --ignored

running 1 test
test
it_works2 ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured


cargo test-- がついているのはミスではなくて、--ignorecargo にではなく実行ファイルへの引数のため。


テストモジュール

テストのときだけビルドされるモジュールを作る。


新規作成

$ cargo new test_example5



ファイル構成

$ tree

| Cargo.toml
|
\---src
lib.rs


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


src/lib.rs

pub fn square(x: i32) -> i32 {

x * x
}


tests/lib.rs

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


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 ディレクトリを作って、その中に記述する。

  • ドキュメント書きに、ドキュメンテーションテストを使おう。