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

  • 26
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

アサーション

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