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

Beginner

This tutorial is from open-source community. Access the source code

はじめに

テストの実行方法の制御へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習できます。

この実験では、cargo testのコマンドラインオプションと結果として生成されるテストバイナリを使用して、Rust でテスト実行の動作を制御する方法を学びます。

テストの実行方法の制御

cargo runがコードをコンパイルしてから生成されたバイナリを実行するのと同じように、cargo testはテストモードでコードをコンパイルして、生成されたテストバイナリを実行します。cargo testによって生成されるバイナリの既定の動作は、すべてのテストを並列で実行し、テスト実行中に生成される出力をキャプチャすることで、出力の表示を防ぎ、テスト結果に関連する出力を読みやすくします。ただし、既定の動作を変更するためにコマンドラインオプションを指定することができます。

一部のコマンドラインオプションはcargo testに渡され、一部は生成されたテストバイナリに渡されます。これら 2 種類の引数を区別するには、cargo testに渡される引数を列挙してから区切り文字--を続け、その後にテストバイナリに渡される引数を列挙します。cargo test --helpを実行すると、cargo testで使用できるオプションが表示され、cargo test -- --helpを実行すると、区切り文字の後で使用できるオプションが表示されます。

並列または連続でテストを実行する

複数のテストを実行する場合、既定ではスレッドを使って並列に実行されます。つまり、実行が早く終了し、より速くフィードバックを得られます。テストが同時に実行されるため、テスト同士や、共有状態(現在の作業ディレクトリや環境変数などの共有環境を含む)に依存しないようにする必要があります。

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

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

cargo test -- --test-threads=1

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

関数の出力を表示する

既定では、テストが合格した場合、Rust のテストライブラリは標準出力に印刷されたものをすべてキャプチャします。たとえば、テスト内でprintln!を呼び出し、テストが合格した場合、端末にはprintln!の出力は表示されません。テストが合格したことを示す行のみが表示されます。テストが失敗した場合、標準出力に印刷されたものとその他の失敗メッセージが表示されます。

例として、リスト 11-10 には、パラメータの値を印刷して 10 を返す単純な関数と、合格するテストと失敗するテストがあります。

ファイル名:src/lib.rs

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!(10, value);
    }

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

リスト 11-10: println!を呼び出す関数のテスト

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

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

failures:

---- tests::this_test_will_fail stdout ----
1 I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
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

合格するテストを実行する際に印刷されるI got the value 4は、この出力のどこにも表示されていないことに注意してください。その出力はキャプチャされています。失敗したテストの出力I got the value 8 [1] は、テストの要約出力のセクションに表示され、テストの失敗原因も示されています。

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

cargo test -- --show-output

--show-outputフラグを使って再度リスト 11-10 のテストを実行すると、次の出力が表示されます。

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

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 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
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

名前でテストのサブセットを実行する

時には、完全なテストスイートを実行するのに長い時間がかかることがあります。特定のコード領域の作業中は、そのコードに関連するテストのみを実行したい場合があります。cargo testに実行したいテストの名前を引数として渡すことで、実行するテストを選ぶことができます。

テストのサブセットを実行する方法を示すために、まずadd_two関数に対して 3 つのテストを作成します(リスト 11-11 を参照)。そして、実行するテストを選びます。

ファイル名:src/lib.rs

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

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

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

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

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

リスト 11-11: 3 つの異なる名前の 3 つのテスト

前述のように、引数を渡さずにテストを実行すると、すべてのテストが並列で実行されます。

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

単一のテストを実行する

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

[object Object]

名前がone_hundredのテストのみが実行されました。他の 2 つのテストはその名前に一致しませんでした。テストの出力は、最後に2 filtered outを表示することで、実行されなかったテストがあることを知らせてくれます。

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

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

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

[object Object]

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

特定の要求がない限り一部のテストを無視する

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

ファイル名:src/lib.rs

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

#[test]
#[ignore]
fn expensive_test() {
    // 1 時間かかる実行コード
}

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

[object Object]

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

[object Object]

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

まとめ

おめでとうございます!「テストの実行方法を制御する」実験を完了しました。LabEx でさらに多くの実験を行って、スキルを向上させましょう。