LoginSignup
62
46

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-12-27

アサーション

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
62
46