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

# アサーション

いわゆる `assert`。

```bash:新規作成
$ cargo new test_example1 --bin
```

```bash:ファイル構成
$ tree
|   Cargo.toml
|
\---src
        main.rs
```

```rust: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);
}
```

```bash:デバッグ実行
$ 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
```

```bash:リリース実行
$ 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!`。

```rust
assert_eq!(a, b)
```

は

```rust
assert!(a == b, "assertion failed: `(left == right)` (left: `{:?}`, right: `{:?}`)", a, b);
```

と記述しているのとおなじ。

## `debug_assert!` マクロ

リリースビルド時に無効化される `assert!`。

## `debug_assert_eq!` マクロ

リリースビルド時に無効化される `assert_eq!`。

# テスト

## `#[test]`

関数に `test` 属性を指定すると、テストとして認識される。

```bash:新規作成
$ cargo new test_example2
```

```bash:ファイル構成
$ tree
|   Cargo.toml
|
\---src
        lib.rs
```

```rust:src/lib.rs
#[test] // test 属性
fn it_works() {
  assert!(false, "always false"); // 必ず失敗する
}

#[test] // test 属性
fn it_works2() {
  assert!(true, "always true"); // 必ず成功する
}
```

`test` 属性のついた関数の中で `panic!` が起こると、テストに失敗したものとして扱われる。

```bash:テスト
$ 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` 属性を付けると、必ず失敗することをテストできる。

```bash:新規作成
$ cargo new test_example3
```

```bash:ファイル構成
$ tree
|   Cargo.toml
|
\---src
        lib.rs
```

```rust: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!` の出力に含まれていれば、ただしく失敗していると判断している。

```bash:テスト
$ 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` 属性で指定できる。

```bash:新規作成
$ cargo new test_example4
```

```bash:ファイル構成
$ tree
|   Cargo.toml
|
\---src
        lib.rs
```

```rust: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");
}
```

```bash:テスト
$ cargo test

running 2 tests
test it_works2 ... ignored
test it_works ... ok

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

```bash:テスト(ignored)
$ 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` にではなく実行ファイルへの引数のため。

## テストモジュール

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

```bash:新規作成
$ cargo new test_example5
```

```bash:ファイル構成
$ tree
|   Cargo.toml
|
\---src
        lib.rs
```

```rust: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` でコンパイル時オプションを指定できる)。

```bash:テスト
$ cargo test

running 1 test
test tests::it_works ... ok

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

これは、ファイル単位の単体テストを記述するときの書き方。

## `tests` ディレクトリ

`tests` ディレクトリを作ると、結合テストを書ける。

```bash:新規作成
$ cargo new test_example6
$ cd test_example6
$ mkdir tests
```

```bash:ファイル構成
$ tree
|   Cargo.toml
|
+---src
|       lib.rs
\---tests
        lib.rs
```

```rust:src/lib.rs
pub fn square(x: i32) -> i32 {
  x * x
}
```

```rust: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);
}
```

```bash:テスト
$ cargo test

running 1 test
test it_works ... ok

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

## ドキュメンテーションテスト

`Rust` は、`//!` で開始するコメントでモジュールの説明を、`///` で開始するコメントで関数の説明を、`Markdown` 形式で記述できる。
ドキュメンテーションテストで、これらのコメントの中に含まれるコードが、ただしく動作するかテストできる。


```bash:新規作成
$ cargo new test_example5
```

```bash:ファイル構成
$ tree
|   Cargo.toml
|
\---src
        lib.rs
```

```rust: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
}
```

```bash:テスト
$ 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` は連番で、複数のコードを書くと増えていく。

ちなみに

```bash:ドキュメント
$ cargo doc
```

でドキュメントを生成できる。

# まとめ

* 単体テストは `#[cfg(test)]` を指定してファイル単位で記述する。
* 統合テストは `tests` ディレクトリを作って、その中に記述する。
* ドキュメント書きに、ドキュメンテーションテストを使おう。