はじめに
テスト組織へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習することができます。
この実験では、Rust コミュニティにおける 2 つの主なテストのカテゴリについて学びます。単体テストは小さく、個々のモジュールを独立してテストすることに焦点が当てられており、統合テストはライブラリの公開インターフェイスを使用し、テストごとに複数のモジュールを実行する可能性があります。
This tutorial is from open-source community. Access the source code
テスト組織へようこそ。この実験は、Rust Bookの一部です。LabEx で Rust のスキルを練習することができます。
この実験では、Rust コミュニティにおける 2 つの主なテストのカテゴリについて学びます。単体テストは小さく、個々のモジュールを独立してテストすることに焦点が当てられており、統合テストはライブラリの公開インターフェイスを使用し、テストごとに複数のモジュールを実行する可能性があります。
本章の冒頭で述べたように、テストは複雑な分野であり、異なる人が異なる用語や組織化を使用します。Rust コミュニティは、2 つの主なカテゴリ:単体テストと統合テストの観点でテストについて考えています。「単体テスト」は小さく、より集中的で、1 回に 1 つのモジュールを独立してテストし、プライベートインターフェイスをテストすることができます。「統合テスト」は、ライブラリの完全に外部にあり、他の外部コードと同じ方法でコードを使用し、公開インターフェイスのみを使用し、テストごとに複数のモジュールを実行する可能性があります。
この 2 種類のテストを記述することは、ライブラリの各部分が個別に、また一緒に期待通りに機能していることを確認するために重要です。
単体テストの目的は、コードの残りの部分から独立して各コードユニットをテストし、コードが期待通りに機能している場所と機能していない場所を迅速に特定することです。単体テストは、テスト対象のコードが含まれる各ファイルの src ディレクトリに配置します。慣例として、各ファイルに tests という名前のモジュールを作成してテスト関数を含め、モジュールに cfg(test) を付けます。
tests モジュールに付けられた #[cfg(test)] アノテーションは、Rust に対して、cargo test を実行するときのみテストコードをコンパイルして実行し、cargo build を実行するときには実行しないように指示します。これにより、ライブラリのみをビルドしたい場合にはコンパイル時間を節約でき、テストが含まれないため、生成されたコンパイル済みアーティファクトのサイズも節約できます。統合テストは別のディレクトリに配置されるため、#[cfg(test)] アノテーションは必要ありません。ただし、単体テストはコードと同じファイルに配置されるため、コンパイル結果に含まれないように #[cfg(test)] を使用します。
本章の最初のセクションで新しい adder プロジェクトを生成したとき、Cargo がこのコードを自動的に生成したことを思い出してください。
ファイル名:src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
このコードは自動生成された tests モジュールです。属性 cfg は「コンフィギュレーション」を意味し、特定のコンフィギュレーションオプションが与えられた場合のみ、次の項目が含まれるべきであることを Rust に伝えます。この場合、コンフィギュレーションオプションは test で、Rust によってテストのコンパイルと実行に提供されます。cfg 属性を使用することで、Cargo は cargo test でテストを積極的に実行した場合にのみ、テストコードをコンパイルします。これには、このモジュール内に含まれる可能性のあるヘルパー関数や、#[test] でアノテートされた関数も含まれます。
プライベート関数を直接テストするべきかどうかについて、テストコミュニティ内では議論があり、他の言語ではプライベート関数をテストすることが困難または不可能になっています。どのテスト思想に従うかに関係なく、Rust のプライバシールールはプライベート関数をテストすることを許可しています。リスト 11-12 のコードを見てみましょう。ここでは、プライベート関数 internal_adder があります。
ファイル名:src/lib.rs
pub fn add_two(a: i32) -> i32 {
internal_adder(a, 2)
}
fn internal_adder(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal() {
assert_eq!(4, internal_adder(2, 2));
}
}
リスト 11-12: プライベート関数のテスト
internal_adder 関数は pub ではないことに注意してください。テストはただの Rust コードであり、tests モジュールはただの別のモジュールです。「モジュールツリー内の項目を参照するためのパス」で説明したように、子モジュール内の項目はその親モジュール内の項目を使用できます。このテストでは、use super::* によって test モジュールの親のすべての項目をスコープに入れ、その後テストでは internal_adder を呼び出すことができます。もしプライベート関数をテストするべきでないと考えている場合は、Rust にはそれを強制するものはありません。
Rust では、統合テストはライブラリの完全に外部にあります。それらは他のコードと同じ方法でライブラリを使用します。つまり、ライブラリのパブリック API の一部である関数のみを呼び出すことができます。その目的は、ライブラリの多くの部分が正しく一緒に機能するかどうかをテストすることです。個別には正しく機能するコードのユニットは、統合された場合に問題が発生する可能性があるため、統合されたコードのテストカバレッジも重要です。統合テストを作成するには、まず tests ディレクトリが必要です。
プロジェクトディレクトリのトップレベルに、src の隣に tests ディレクトリを作成します。Cargo はこのディレクトリ内の統合テストファイルを探すようになっています。その後、必要なだけ多くのテストファイルを作成でき、Cargo は各ファイルを個別のクレートとしてコンパイルします。
統合テストを作成しましょう。src/lib.rs ファイルにまだリスト 11-12 のコードがある状態で、tests ディレクトリを作成し、新しいファイル tests/integration_test.rs を作成します。ディレクトリ構造は以下のようになります。
adder
├── Cargo.lock
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
└── integration_test.rs
リスト 11-13 のコードを tests/integration_test.rs ファイルに入力します。
ファイル名:tests/integration_test.rs
use adder;
#[test]
fn it_adds_two() {
assert_eq!(4, adder::add_two(2));
}
リスト 11-13: adder クレート内の関数の統合テスト
tests ディレクトリ内の各ファイルは別個のクレートであるため、ライブラリを各テストクレートのスコープに入れる必要があります。そのため、コードの上部に use adder; を追加します。これは単体テストでは必要ありませんでした。
tests/integration_test.rs 内のコードには #[cfg(test)] でアノテートする必要はありません。Cargo は tests ディレクトリを特別扱いし、cargo test を実行したときのみこのディレクトリ内のファイルをコンパイルします。今 cargo test を実行してみましょう。
[object Object]
出力の 3 つのセクションには、単体テスト、統合テスト、ドキュメントテストが含まれています。セクション内のテストが 1 つでも失敗すると、次のセクションは実行されません。たとえば、単体テストが失敗すると、統合テストとドキュメントテストの出力はありません。なぜなら、それらのテストはすべての単体テストが合格した場合にのみ実行されるからです。
単体テストの最初のセクション[1]は、これまで見てきたものと同じです。各単体テストに 1 行(リスト 11-12 で追加した internal という名前のもの)があり、その後に単体テストのサマリー行があります。
統合テストのセクションは、Running tests/integration_test.rs という行で始まります[2]。次に、その統合テスト内の各テスト関数に 1 行があり[3]、Doc-tests adder セクションが始まる直前に統合テストの結果のサマリー行があります[4]。
各統合テストファイルには独自のセクションがあるため、tests ディレクトリにさらにファイルを追加すると、統合テストのセクションが増えます。
特定の統合テスト関数を実行するには、cargo test に対してテスト関数の名前を引数として指定します。特定の統合テストファイル内のすべてのテストを実行するには、cargo test の --test 引数に続けてファイル名を指定します。
[object Object]
このコマンドは、tests/integration_test.rs ファイル内のテストのみを実行します。
統合テストを増やすにつれて、tests ディレクトリにさらにファイルを作成して整理することが望ましくなる場合があります。たとえば、テスト関数をテストする機能ごとにグループ化することができます。前述のとおり、tests ディレクトリ内の各ファイルは個別のクレートとしてコンパイルされます。これは、個別のスコープを作成して最終ユーザーがクレートを使用する方法により密接に似せるために役立ちます。ただし、これは、tests ディレクトリ内のファイルが、第 7 章でコードをモジュールとファイルに分離する方法に関して学んだ通り、src 内のファイルと同じ動作を共有しないことを意味します。
tests ディレクトリのファイルの異なる動作は、複数の統合テストファイルで使用する一連のヘルパー関数があり、「モジュールを別のファイルに分離する」の手順に従ってそれらを共通のモジュールに抽出しようとするときに最も顕著になります。たとえば、tests/common.rs を作成し、その中に setup という名前の関数を配置すると、複数のテストファイルの複数のテスト関数から呼び出したいコードを setup に追加できます。
ファイル名:tests/common.rs
pub fn setup() {
// setup code specific to your library's tests would go here
}
テストを再度実行すると、common.rs ファイル用の新しいセクションがテスト出力に表示されます。このファイルにはテスト関数が含まれておらず、どこからも setup 関数を呼び出していません。
running 1 test
test tests::internal... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
Running tests/common.rs (target/debug/deps/common-
92948b65e88960b4)
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
Running tests/integration_test.rs
(target/debug/deps/integration_test-92948b65e88960b4)
running 1 test
test it_adds_two... ok
test result: ok. 1 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
common が running 0 tests が表示された状態でテスト結果に表示されるのは、私たちが望んだものではありません。他の統合テストファイルとコードを共有したかっただけです。common がテスト出力に表示されないようにするには、tests/common.rs を作成する代わりに、tests/common/mod.rs を作成します。現在のプロジェクトディレクトリは次のようになります。
├── Cargo.lock
├── Cargo.toml
├── src
│ └── lib.rs
└── tests
├── common
│ └── mod.rs
└── integration_test.rs
これは、「代替ファイルパス」で説明した、Rust が理解する古い命名規則です。このようにファイル名を付けることで、Rust に対して common モジュールを統合テストファイルとして扱わないように伝えます。setup 関数のコードを tests/common/mod.rs に移動し、tests/common.rs ファイルを削除すると、テスト出力のセクションはもはや表示されません。tests ディレクトリのサブディレクトリ内のファイルは、個別のクレートとしてコンパイルされたり、テスト出力にセクションが表示されたりしません。
tests/common/mod.rs を作成した後、統合テストファイルのいずれかからモジュールとして使用できます。以下は、tests/integration_test.rs の it_adds_two テストから setup 関数を呼び出す例です。
ファイル名:tests/integration_test.rs
use adder;
mod common;
#[test]
fn it_adds_two() {
common::setup();
assert_eq!(4, adder::add_two(2));
}
mod common; 宣言は、リスト 7-21 で示したモジュール宣言と同じであることに注意してください。そして、テスト関数内では、common::setup() 関数を呼び出すことができます。
もし私たちのプロジェクトが、src/main.rs ファイルのみを含み、src/lib.rs ファイルを持たないバイナリクレートである場合、tests ディレクトリに統合テストを作成して、src/main.rs ファイルで定義された関数を use 文でスコープに入れることはできません。他のクレートが使用できる関数を公開するのはライブラリクレートだけであり、バイナリクレートは独自に実行されることを意図されています。
これは、バイナリを提供する Rust プロジェクトが、src/lib.rs ファイルに存在するロジックを呼び出す単純な src/main.rs ファイルを持つ理由の一つです。その構造を使用することで、統合テストは use を使ってライブラリクレートをテストして、重要な機能を利用できるようになります。重要な機能が機能すれば、src/main.rs ファイルの少量のコードも機能し、その少量のコードはテストする必要がありません。
おめでとうございます!あなたは「テストの組織化」の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに多くの実験を練習することができます。