テスト

知っての通り、テストはどんなソフトウェアにも不可欠です!Rustはユニットテストと統合テストを第一級にサポートしています(TRPLのこの章を参照してください)。

上のリンク先のテストの章では、ユニットテストと統合テストの書き方を紹介しています。ユニットテストはテスト対象のモジュール内に、統合テストはtests/ディレクトリ内に置きます。

foo
├── Cargo.toml
├── src
│   └── main.rs
│   └── lib.rs
└── tests
    ├── my_test.rs
    └── my_other_test.rs

Each file in tests is a separate integration test, i.e. a test that is meant to test your library as if it were being called from a dependent crate.

The Testing chapter elaborates on the three different testing styles: Unit, Doc, and Integration.

cargoは、全てのテストを簡単に実行する方法を提供します。

$ cargo test

出力はこのようになります。

$ cargo test
   Compiling blah v0.1.0 (file:///nobackup/blah)
    Finished dev [unoptimized + debuginfo] target(s) in 0.89 secs
     Running target/debug/deps/blah-d3b32b97275ec472

running 4 tests
test test_bar ... ok
test test_baz ... ok
test test_foo_bar ... ok
test test_foo ... ok

test result: ok. 4 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

パターンにマッチする名前のテストを実行することもできます。

$ cargo test test_foo
$ cargo test test_foo
   Compiling blah v0.1.0 (file:///nobackup/blah)
    Finished dev [unoptimized + debuginfo] target(s) in 0.35 secs
     Running target/debug/deps/blah-d3b32b97275ec472

running 2 tests
test test_foo ... ok
test test_foo_bar ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out

One word of caution: Cargo may run multiple tests concurrently, so make sure that they don't race with each other.

並行性が問題を引き起こす一例として、以下のように、2つのテストが1つのファイルに出力するケースがあります。

#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
    // 必要なモジュールをインポートします。
    use std::fs::OpenOptions;
    use std::io::Write;

    // ファイルに書き込むテスト
    #[test]
    fn test_file() {
        // ferris.txtというファイルを開くか、存在しない場合は作成します。
        let mut file = OpenOptions::new()
            .append(true)
            .create(true)
            .open("ferris.txt")
            .expect("Failed to open ferris.txt");

        // "Ferris"と5回書き込みます。
        for _ in 0..5 {
            file.write_all("Ferris\n".as_bytes())
                .expect("Could not write to ferris.txt");
        }
    }

    // 同じファイルに書き込むテスト
    #[test]
    fn test_file_also() {
        // ferris.txtというファイルを開くか、存在しない場合は作成します。
        let mut file = OpenOptions::new()
            .append(true)
            .create(true)
            .open("ferris.txt")
            .expect("Failed to open ferris.txt");

        // "Corro"と5回書き込みます。
        for _ in 0..5 {
            file.write_all("Corro\n".as_bytes())
                .expect("Could not write to ferris.txt");
        }
    }
}
}

以下のような結果を得ようと意図しています。

$ cat ferris.txt
Ferris
Ferris
Ferris
Ferris
Ferris
Corro
Corro
Corro
Corro
Corro

しかし、実際にferris.txtに出力されるのは、以下の通りです。

$ cargo test test_file && cat ferris.txt
Corro
Ferris
Corro
Ferris
Corro
Ferris
Corro
Ferris
Corro
Ferris