Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

テストの実行方法を制御する

cargo run がコードをコンパイルして生成されたバイナリを実行するのと同様に、cargo test はコードをテストモードでコンパイルし、生成されたテストバイナリを実行します。cargo test によって生成されるバイナリのデフォルトの挙動では、すべてのテストを並列に実行し、テスト実行中に生成された出力をキャプチャします。これにより出力は表示されなくなり、テスト結果に関する出力を読みやすくできます。ただし、このデフォルトの挙動はコマンドラインオプションで変更できます。

一部のコマンドラインオプションは cargo test に渡され、別のものは生成されたテストバイナリに渡されます。これら 2 種類の引数を分けるには、まず cargo test に渡す引数を並べ、その後に区切り文字 -- を置き、続けてテストバイナリに渡す引数を指定します。cargo test --help を実行すると cargo test で使用できるオプションが表示され、cargo test -- --help を実行すると区切り文字の後で使用できるオプションが表示されます。これらのオプションは、The rustc Book の「Tests」セクション にも記載されています。

テストを並列または順番に実行する

複数のテストを実行すると、デフォルトではスレッドを使って並列に実行されるため、より早く完了し、より早くフィードバックを得られます。テストは同時に実行されるため、テスト同士が互いに依存していないこと、また現在の作業ディレクトリや環境変数のような共有環境を含む、いかなる共有状態にも依存していないことを必ず確認しなければなりません。

たとえば、それぞれのテストがディスク上に test-output.txt という名前のファイルを作成し、そのファイルに何らかのデータを書き込むコードを実行するとします。次に、各テストがそのファイルのデータを読み取り、ファイルに特定の値が含まれていることをアサートするとします。その値は各テストで異なります。テストは同時に実行されるため、あるテストがファイルを書き込んでから読み取るまでの間に、別のテストがそのファイルを上書きしてしまうかもしれません。すると後者のテストは失敗しますが、それはコードが誤っているからではなく、並列実行中にテスト同士が干渉したためです。1 つの解決策は、各テストが別々のファイルに書き込むようにすることです。別の解決策は、テストを 1 つずつ実行することです。

テストを並列実行したくない場合や、使用するスレッド数をより細かく制御したい場合は、--test-threads フラグと使用したいスレッド数をテストバイナリに渡せます。次の例を見てください:

$ cargo test -- --test-threads=1

ここではテストスレッド数を 1 に設定しており、プログラムに並列実行を行わないよう伝えています。1 つのスレッドでテストを実行すると、並列実行する場合より時間はかかりますが、共有状態があってもテスト同士が干渉しなくなります。

関数の出力を表示する

デフォルトでは、テストが成功した場合、Rust のテストライブラリは標準出力に表示されたものをすべてキャプチャします。たとえば、テスト内で println! を呼び出してそのテストが成功しても、ターミナルには println! の出力は表示されず、そのテストが成功したことを示す行だけが表示されます。テストが失敗した場合は、失敗メッセージの残りの部分とともに、標準出力に表示された内容がすべて表示されます。

例として、リスト 11-10 には、引数の値を表示して 10 を返すくだらない関数と、成功するテストおよび失敗するテストがあります。

fn prints_and_returns_10(a: i32) -> i32 {
    println!("I got the value {a}");
    10
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(value, 10);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(value, 5);
    }
}

これらのテストを cargo test で実行すると、次のような出力が表示されます:

$ cargo test
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.58s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8

thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
  left: 10
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

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

error: test failed, to rerun pass `--lib`

この出力のどこにも、成功するテストの実行時に表示される I got the value 4 が見当たらないことに注意してください。その出力はキャプチャされています。失敗したテストからの出力である I got the value 8 は、テスト失敗の原因も示されるテスト概要出力のセクションに現れます。

成功したテストについても表示された値を見たい場合は、--show-output を使って、成功したテストの出力も表示するよう Rust に指示できます:

$ cargo test -- --show-output

リスト 11-10 のテストを --show-output フラグ付きでもう一度実行すると、次のような出力が表示されます:

$ cargo test -- --show-output
   Compiling silly-function v0.1.0 (file:///projects/silly-function)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/silly_function-160869f38cff9166)

running 2 tests
test tests::this_test_will_fail ... FAILED
test tests::this_test_will_pass ... ok

successes:

---- tests::this_test_will_pass stdout ----
I got the value 4


successes:
    tests::this_test_will_pass

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8

thread 'tests::this_test_will_fail' panicked at src/lib.rs:19:9:
assertion `left == right` failed
  left: 10
 right: 5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace


failures:
    tests::this_test_will_fail

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

error: test failed, to rerun pass `--lib`

名前でテストの一部を実行する

テストスイート全体の実行には、時間がかかることがあります。コードの特定の領域を作業している場合、そのコードに関連するテストだけを実行したいかもしれません。cargo test に、実行したいテストの名前を引数として渡すことで、どのテストを実行するかを選べます。

テストの一部を実行する方法を示すために、まずリスト 11-11 に示すように add_two 関数に対する 3 つのテストを作成し、どれを実行するか選びます。

pub fn add_two(a: u64) -> u64 {
    a + 2
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn add_two_and_two() {
        let result = add_two(2);
        assert_eq!(result, 4);
    }

    #[test]
    fn add_three_and_two() {
        let result = add_two(3);
        assert_eq!(result, 5);
    }

    #[test]
    fn one_hundred() {
        let result = add_two(100);
        assert_eq!(result, 102);
    }
}

先ほど見たように、引数を何も渡さずにテストを実行すると、すべてのテストが並列に実行されます:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.62s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 3 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok
test tests::one_hundred ... ok

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

   Doc-tests adder

running 0 tests

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

単一のテストを実行する

任意のテスト関数の名前を cargo test に渡すことで、そのテストだけを実行できます:

$ cargo test one_hundred
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.69s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::one_hundred ... ok

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

実行されたのは one_hundred という名前のテストだけで、ほかの 2 つのテストはその名前に一致しませんでした。テスト出力の末尾に 2 filtered out と表示されることで、実行されなかったテストがほかにもあったことがわかります。

この方法では複数のテスト名を指定できません。cargo test に与えた最初の値だけが使用されます。しかし、複数のテストを実行する方法はあります。

フィルタリングして複数のテストを実行する

テスト名の一部を指定すると、その値に一致する名前を持つテストが実行されます。たとえば、私たちのテストのうち 2 つの名前に add が含まれているので、cargo test add を実行することでその 2 つを実行できます:

$ cargo test add
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test tests::add_three_and_two ... ok
test tests::add_two_and_two ... ok

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

このコマンドは、名前に add を含むすべてのテストを実行し、one_hundred という名前のテストを除外しました。また、テストが含まれるモジュールはテスト名の一部になることにも注意してください。そのため、モジュール名でフィルタリングすることで、そのモジュール内のすべてのテストを実行できます。

明示的に要求された場合を除いてテストを無視する

ときには、いくつかの特定のテストの実行に非常に時間がかかることがあるため、cargo test の大半の実行ではそれらを除外したい場合があります。実行したいすべてのテストを引数として列挙する代わりに、ここに示すように、時間のかかるテストに ignore 属性を付けて除外できます:

ファイル名: src/lib.rs

```rust,noplayground
# pub fn add(left: u64, right: u64) -> u64 {
#     left + right
# }
# 
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_works() {
        let result = add(2, 2);
        assert_eq!(result, 4);
    }

    #[test]
    #[ignore]
    fn expensive_test() {
        // code that takes an hour to run
    }
}

#[test] の後に、除外したいテストに #[ignore] 行を追加します。 これでテストを実行すると、it_works は実行されますが、expensive_test は実行されません:

$ cargo test
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.60s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 2 tests
test tests::expensive_test ... ignored
test tests::it_works ... ok

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

   Doc-tests adder

running 0 tests

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

expensive_test 関数は ignored として表示されています。無視されたテストだけを実行したい 場合は、cargo test -- --ignored を使用できます:

$ cargo test -- --ignored
   Compiling adder v0.1.0 (file:///projects/adder)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.61s
     Running unittests src/lib.rs (target/debug/deps/adder-92948b65e88960b4)

running 1 test
test tests::expensive_test ... ok

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

   Doc-tests adder

running 0 tests

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

どのテストを実行するかを制御することで、cargo test の結果が すばやく返ってくるようにできます。ignored テストの結果を確認するのが適切な 段階にあり、その結果を待つ時間があるなら、代わりに cargo test -- --ignored を 実行できます。無視されているかどうかにかかわらず、すべてのテストを実行したい 場合は、cargo test -- --include-ignored を実行できます。