11章:自動テストを書く
目次
11.0 概要
テストの実行
-
テストは
cargo test
で実行できる -
cargo test --(cargo test にかかるオプション) --(テストバイナリにかかるオプション)
というふうにオプションを追加できる- (cf.
cargo test
はコードをテストモードでコンパイルし、出来上がったテストバイナリを実行することに注意 - この文書では
cargo test
のオプションとして以下を紹介:cargo test -- --help
cargo test -- --test-threads=1
cargo test -- --nocapture
- (cf.
-
また、
cargo test テスト関数名の一部
と指定すると指定したテキストを名前の一部に含むテスト関数のみが実行される
テストの定義
-
Rust では
fn
の前に#[test]
が付与してあれば、テスト用の関数とみなされる -
#[test]
に加えて#[ignore]
が付与された関数は基本的に実行されない-
cargo test -- --ignored
を実行すれば、#[ignore]
が付与されたテストのみを実行できる
-
-
cargo new hoge --lib
実行時(ライブラリプロジェクト作成時)には、自動でテストモジュールが作成される -
各種マクロ:
-
assert!(b)
:b
がtrue
かチェック -
assert_eq!(left, right)
:left == right
かチェック -
assert_ne!(left, right)
:left != right
かチェック
-
-
各種マクロの第3引数にフォーマット、それ以降にフォーマットに組み込む値を列挙すると、失敗時のメッセージを定義できる
-
#[test]
に加えて#[should_panic]
を付与すると関数内のコードがパニックしたら、テストを通過させる- 以下のように制約を加えることも可能:
#[should_panic(expected = "失敗メッセージに含まれていることを期待するメッセージ内容")]
- 以下のように制約を加えることも可能:
-
#[test]
が付与された関数内の返り値の型をResult<T, E>
にすると、「Ok(...)
が返るとテスト成功」「Err(...)
が返るとテスト失敗」となる- このようなテスト関数内では
?
を使えるので便利
- このようなテスト関数内では
単体テストの慣習
- 単体テストのテスト関数は、
src
ディレクトリの各ファイル内でテスト対象のコードと共に記述する - 以下のようにテスト用のモジュールを定義する:
// ... テスト対象のコード
#[cfg(test)]
mod tests {
#[test]
fn hoge () {
// テスト関数の内容
}
// ...他のテスト関数
}
-
#[cfg(test)]
で注釈されたモジュールはcargo build
時には無視されて、cargo test
時にのみコンパイルされる
結合テストの慣習
ライブラリクレートの場合
-
src
と同階層にtests
ディレクトリを作成-
tests/hoge.rs
内にテストを記述 - なお、
#[cfg(test)]
注釈は不要 -
extern crate hoge;
のようにクレートを導入すること
-
-
特定のテストファイルを実行するには、
cargo test --test テストファイル名(拡張子抜き)
する -
tests
ディレクトリのサブディレクトリ内のファイルは個別クレートとしてコンパイルされたり、テスト出力に区域が表示されることがない- →
tests/hoge/fuga.rs
を作成することで、各テストに共通の関数などを定義できる
- →
バイナリクレートの場合
- バイナリを提供する Rust のプロジェクトの基本形は、
src/lib.rs
ファイルに存在するロジックを呼び出す単純なsrc/main.rs
ファイルとするべし! - あとのテスト手順は ライブラリクレートの場合 に従えばよい
11.1 テストの記述法
- テスト関数の本体は、典型的には以下の 3 つの動作を行う
- 必要なデータや状態をセットアップ
- テスト対象のコードを走らせる
- 結果が想定通りであることを断定(アサーション)する
11.2 テスト関数の構成
- Rust におけるテストは test 属性で注釈された関数
- 関数をテスト関数に変えるには、
fn
の前に#[test]
を付け加える -
cargo test
コマンドでテストを実行すると- コンパイラは
test
属性で注釈された関数を走らせるテスト用バイナリをビルド - 各テスト関数が通過したか失敗したかを報告する
- コンパイラは
ライブラリプロジェクトの生成とテスト関数の自動生成
-
新しいライブラリプロジェクトを Cargo で作ると、テスト関数付きのテストモジュールが自動的に生成される
cargo new adder --lib
-
テストを実行してみる
$ cargo test Compiling adder v0.1.0 (/home/flip451/Oniwa/tutorial/t-rust/t3-the-book/projects/adder) Finished test [unoptimized + debuginfo] target(s) in 0.33s Running unittests (target/debug/deps/adder-9e0eca6a61a7cecc) running 1 test test tests::it_works ... 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
各種マクロ (assert!
, assert_eq!
, assert_ne!
)
-
assert!(bool)
-->bool
の値がTrue
になることを期待する -
assert_eq!(left, right)
とassert_ne!(left, right)
-->left
とright
の等値性をテストする- 比較対象の値は
PartialEq
とDebug
トレイトを実装していなければならない(理由は以下の通り)-
assert_eq!
とassert_ne!
マクロは、それぞれ==
と!=
演算子を使用. - 引数をデバッグフォーマットを使用してプリントする.
-
- => 構造体や enum 定義に
#[derive(PartialEq, Debug)]
という注釈を追加すればよい - 失敗メッセージと共にカスタムのメッセージが表示されるよう、追加することもできる(第3引数にフォーマット、それ以降にフォーマットに組み込む値を列挙する)
-
例:
#[test] fn greeting_contains_name() { let result = greeting("Carol"); assert!( result.contains("Carol"), "Greeting did not contain name, value was `{}`", result ); }
-
- 比較対象の値は
#[should_panic]
-
#[should_panic]
--> 関数内のコードがパニックしたら、テストを通過させる#[test] #[should_panic] fn another() { panic!() }
-
expected
引数を追加して、失敗メッセージに与えられたテキストが含まれていることを確かめる#[test] #[should_panic(expected = "Guess value must be less than or equal to 100")] fn greater_than_100() { Guess::new(200); }
Resultをテストで使う
-
例:関数内で
assert_eq!
マクロを呼び出す代わりにテストが成功すればOk(())
を、失敗すればErr
にString
を入れて返す#[test] fn it_works() -> Result<(), String> { if 2 + 2 == 4 { Ok(()) } else { Err(String::from("two plus two does not equal four")) } }
-
Result<T, E>
を返すようなテストを書くと、?
演算子をテストの中で使える -
Result<T, E>
を使うテストに#[should_panic]
注釈を使うことはできないので注意
11.3 テストの実行のされ方を制御する
-
cargo test
はコードをテストモードでコンパイルし、出来上がったテストバイナリを実行 - コマンドラインオプションを指定して
cargo test
の既定動作を変更することができるcargo test --(cargo test にかかるオプション) --(テストバイナリにかかるオプション)
- 例:
cargo test --help
:cargo test
で使用できるオプションの一覧を表示 - 例:
cargo test -- --help
:--
という区分記号の後に使えるオプションの一覧を表示
テストを並行または連続して実行する
-
複数のテストを実行するとき、標準では、スレッドを使用して並行に走る
-
並行にテストを実行したくなかったり、使用されるスレッド数をよりきめ細かく制御したい場合 -->
--test-threads
フラグと使用したいスレッド数をテストバイナリに送る -
例:
cargo test -- --test-threads=1
関数の出力を表示する
-
標準ではテスト時に標準出力にはテスト結果と、失敗したテストからの標準出力しか表示されない
-
通過するテストについても出力される値が見たかったら、出力キャプチャ機能を
--nocapture
フラグで無効化することができる -
例:
cargo test -- --nocapture
名前でテストの一部を実行する
-
cargo test
に走らせたいテストの名前を引数として渡すことで、実行するテストを選ぶことができる -
例:
one_hundred
というテスト関数だけ実行するcargo test one_hundred
-
例:テスト名の一部を指定して、その値に合致するあらゆるテストを走らせる
$ cargo test add (add と名の付くすべてのテスト関数が実行される)
-
テスト名には、テスト関数を含むモジュール名も含むので、モジュール名でフィルターをかけることで、あるモジュール内のテスト全てを実行できる
特に要望のない限りテストを無視する(#[ignore]
)
-
#[ignore]
属性でテストを除外する -
例:
#[test] fn it_works() { assert_eq!(2 + 2, 4); } #[test] #[ignore] fn expensive_test() { // code that takes an hour to run }
-
無視されるテストのみを実行したかったら、
cargo test -- --ignored
を使う
11.4 テストの体系化
テストの種類
- 単体テスト
- 個別に 1 回に 1 モジュールをテスト
- 非公開のインターフェイスもテストする
- 結合テスト
- 外部コード同様に自分のコードを使用
- 公開インターフェイスのみ使用
- 1 テストにつき複数のモジュールを用いることもある
単体テスト
- テスト対象となるコードと共に、
src
ディレクトリの各ファイルに置く - 慣習にしたがえば、各ファイルに
tests
という名前のモジュールを作り、テスト関数を含ませ、そのモジュールを#[cfg(test)]
で注釈する
#[cfg(test)]
-
#[cfg(test)]
という注釈は、cargo build
した時ではなく、cargo test
した時だけ、テストコードをコンパイルし走らせるよう指示 - 単体テストはコードと同じファイルに存在するので、
#[cfg(test)]
を使用してコンパイル結果に含まれないよう指定する-
cfg
という属性は、configuration を表していて、コンパイラに続く要素が、ある特定の設定オプションを与えられたら、含まれるように指示する - たとえば、
#[cfg(test)]
とすればcargo test
で積極的にテストを実行した場合のみ、Cargo がテストコードをコンパイルする
-
結合テスト
ライブラリクレート用の結合テスト
-
結合テストを作成するには、まず
tests
ディレクトリが必要になる- プロジェクトディレクトリのトップ階層、
src
の隣にtests
ディレクトリを作成 - このディレクトリ内にいくらでもテストファイルを作成可能
- Cargo はそれぞれのファイルを個別のクレートとしてコンパイルする
-
Cargo
はtests
ディレクトリを特別に扱い、cargo test
を走らせた時にのみこのディレクトリのファイルをコンパイルする- -->
tests/hoge.rs
内のコードは#[cfg(test)]
で注釈する必要がない
- -->
- プロジェクトディレクトリのトップ階層、
-
特定のテストファイルを実行させるには、
cargo test --test <テストファイル名(拡張子抜き)>
とすればよい$ cargo test --test integration_test (このコマンドは、`tests/integration_test.rs` ファイルにあるテストのみを実行)
-
tests
ディレクトリのサブディレクトリ内のファイルは個別ク
レートとしてコンパイルされたり、テスト出力に区域が表示されることがない-
-->
tests/hoge/mod.rs
を作成することで、テスト結果の出力には影響を与えずに、共通関数などを定義できる- (
tests/hoge/mod.rs
で定義した関数をtests/fuga.rs
から呼び出すことができる、)
- (
-
例:
tests/integration_test.rs
extern crate adder; mod common; #[test] fn it_adds_two() { common::setup(); assert_eq!(4, adder::add_two(2)); }
tests/common/mod.rs
pub fn setup() { println!("do nothing..."); }
-
バイナリクレート用の結合テスト
もしもプロジェクトが src/main.rs
ファイルのみを含み、src/lib.rs
ファイルを持たないバイナリクレートだったら...
-
tests
ディレクトリに結合テストを作成し、extern crate <パッケージ名>;
を使用してsrc/main.rs
ファイルに定義された関数をインポートすることはできない -
extern crate <パッケージ名>;
を使用して呼び出せるのはいつでもsrc/lib.rs
で定義されるクレート
--> バイナリを提供する Rust のプロジェクトの基本形は、
src/lib.rs
ファイルに存在するロジックを呼び出す単純な src/main.rs
ファイルとすべき
- この形なら、
extern crate <パッケージ名>;
を使用してプロジェクトの主要な機能をテストすることができる - ただし、
src/main.rs
ファイルの少量のコードはテストできないので注意