📝

[Rust] プログラムのテスト < cargo test >

2022/08/26に公開約5,800字

プログラムのテスト機能 #[test]

テストの実装

  • cargoには自作した関数やクレートの動作確認に役立つtestの機能がある
  • #[test]のアトリビュートを付けることでテスト用の関数として扱われる
テストの実行
#[test]         // test コマンド時に実行される
fn test_foo() {
    println!("msg_test_foo");
}

fn main() {
    println!("main");
}

テストの実行とテスト結果

  • 下記のコマンドでテストが実行され、テスト結果が表示される
  • テスト実行時はmain関数内は実行されない
  • テスト内での出力(prinln!()など)は表示されない
テスト実行コマンド(ターミナル)
cargo test
実行結果(ターミナル)
running 1 test
test test_foo ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
項目 内容
test result テストの総合結果
passed 結果が正常だったテストの数
failed 結果が異常だったテストの数
ignored #[ignore]により無視されたテストの数
measured ベンチマーク結果(nightly限定)
filtered out フィルタにより無視されたテストの数
finished in テストの実行時間

テスト出力の表示

  • 下記のオプションでテスト内での出力を表示することも可能
テスト実行コマンド(ターミナル)
cargo test -- --nocapture
実行結果(ターミナル)
running 1 test
msg_test_foo
test test_foo ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

テストのフィルタリング

複数のテストの実行

  • テストが複数存在した状態でテストを実行すると、全てのテストが同時に実行される
テストの複数実行
#[test]         // テスト1
fn test_foo() {
    println!("msg_test_foo");
}

#[test]         // テスト2
fn test_bar() {
    println!("msg_test_bar");
}

#[test]         // テスト3
fn test_baz() {
    println!("msg_test_baz");
}

fn main() {
    // メイン処理
}
実行結果(ターミナル)
running 3 tests
test test_baz ... ok
test test_bar ... ok
test test_foo ... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

テストの名前を指定して実行

  • テストの名前を指定することで実行するテストのフィルタリングが可能
テスト実行コマンド(ターミナル)
cargo test test_foo
実行結果(ターミナル)
running 1 test
test test_foo ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s

テストの名前を部分指定して実行

  • フィルタリングの名前は部分一致のため、下記ではtest_bartest_bazが実行される
テスト実行コマンド(ターミナル)
cargo test test_b
実行結果(ターミナル)
running 2 tests
test test_bar ... ok
test test_baz ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

テストの無視 #[ignore]

任意のテストを無視

  • 普段実行しないテストは#[ignore]のアトリビュートにより無視することが可能
テストの無視
#[test]
fn test_foo() {
    println!("msg_test_foo");
}

#[test]
#[ignore]       // 通常のテストでは無視される
fn test_bar() {
    println!("msg_test_bar");
}

fn main() {
    // メイン処理
}
実行結果(ターミナル)
running 2 tests
test test_bar ... ignored
test test_foo ... ok

test result: ok. 1 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 0.00s

無視したテストの実行

  • 下記のオプションで#[ignore]を付けているテストのみが実行される
テスト実行コマンド(ターミナル)
cargo test -- --ignored
実行結果(ターミナル)
running 1 test
test test_bar ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 1 filtered out; finished in 0.00s

テストのエラー無視 #[should_panic]

テストのエラー

  • テストでエラーがあった場合、下記のような結果が表示される
テストのエラー
#[test]
fn test_foo() {
    println!("msg_test_foo");
    panic!();       // 意図的にエラー
}

fn main() {
    // メイン処理
}
実行結果(ターミナル)
running 1 test
test test_foo ... FAILED

failures:

---- test_foo stdout ----
msg_test_foo
thread 'test_foo' panicked at 'explicit panic', src\main.rs:4:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    test_foo

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

error: test failed, to rerun pass '--bin attribute_test'

エラーの無視

  • エラーが前提のテストを実行する場合、#[should_panic]のアトリビュートによりエラーを正常として扱うようにできる
テストのエラー無視
#[test]
#[should_panic]     // パニック前提のテスト
fn test_foo() {
    println!("msg_test_message");
    panic!();
}

fn main() {
    // メイン処理
}
実行結果(ターミナル)
running 1 test
test test_foo - should panic ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

テスト用マクロ

  • テスト用に下記のようなマクロが用意されている
テスト用マクロ
assert!(x);         // x が ture ならば正常、異なればパニック
assert_eq!(x, y);   // x == y ならば正常、異なればパニック
assert_ne!(x, y);   // x != y ならば正常、異なればパニック
assertの使用例
#[test]
fn test_foo() {
    assert!(add(1, 2) ==  3);       // 正常( 1 + 2 == 3 -> true )
    assert_eq!(add(1, 2), 3);       // 正常( 1 + 2 == 3 )
    assert_ne!(add(1, 2), 4);       // 正常( 1 + 2 != 4 )

    assert_eq!(add(1, 2), 1);       // エラー( 1 + 2 == 1 )
}

fn add(x: u32, y: u32) -> u32 {     // 自作の関数
    x + y
}

fn main() {
    // メイン処理
}

戻り値がResult型のテスト

  • ?演算子を使ったResult型でエラーを返すことも可能
戻り値がResult型のテスト
use std::io::Result;
use std::fs::write;

#[test]
fn test_foo() -> Result<()> {                   // 戻り値がstd::io::Result型
    write("test.txt", "create text file")?;     // ? 演算子が使える関数の例
    Ok(())                                      // 異常が無ければ最後に Ok(()) を返す
}

fn main() {
    // メイン処理
}

テストの実装場所

プログラムソース

  • プログラムのソース内に直接テストを実装する
プログラムソース
#[test]
fn test_foo() {
    println!("msg_test_foo");
}

fn main() {
    // メイン処理
}

プログラムソース内モジュール

  • プログラムのソース内にモジュールを作成して実装する
  • #[cfg(test)]のアトリビュートにより、モジュールがテスト時のみビルドされる
  • テストでしか使わないクレートなどをモジュールに入れれば通常のビルドの時間を短くできる
#[cfg(test)]        // テスト時のみビルドされる
mod test {      // テスト用モジュール
    #[test]
    fn test_foo() {
        println!("msg_test_foo");
    }
}

fn main() {
    // メイン処理
}

testsディレクトリ

  • testsディレクトリ内のソースファイルは自動的にテスト扱いにされる
  • 各ソースファイルを跨いで動作するのでスコープの問題が起きない
  • 最終的な統合テストに使える
ディレクトリ構造
project
 ├─ .git
 ├─ src
 │   └─ main.rs
 ├─ target
 └─ tests
      └─ test.rs
\tests\test.rs
#[test]
fn test_foo() {
    println!("msg_test_foo");
}

ドキュメンテーションコメント

  • 関数定義などのドキュメンテーションコメントの``` ```内に実装できる
  • 自作の関数が意図せず書き変わっていないかの確認に使える
lib.rs
///
/// ```
/// println!("msg_test_foo");
/// ````
/// 
pub fn func() {     // 自作の関数
    // 処理内容
}

Discussion

ログインするとコメントできます