https://doc.rust-lang.org/book/second-edition/
Apache License Version 2.0
Testing
Rustはコードの中にテストを書き込むことを許している.
Controlling How Tests are Run
cargo test
にオプションを与えてデフォルトとは異なる動作をさせることが出来る.オプションはcargo test --aaa --bbb
というふうに書く.cargo test
そのものに与えるオプションはaaa
に,生成されるバイナリに与えるオプションはbbb
に列挙する.
Running Tests in Parallel or Consecutively
デフォルトでは,それぞれのテストはいちいち結果を出力せずに平行実行され,最後に全ての結果を出力する.これは高速だが,以前行ったテストの結果や途中の計算を利用したテストを行えないという問題が有る.たとえばtest-output.txt
というファイルを作成し,その中に何かしらのデータを書き込み,そのファイルがテストごとに異なる特定の値を含んでいるという一連のテストを考える.平行実行によるテストでは同じ名前のファイルの作成によって上書きが生じてしまい,正しいコードでもテストに通らなくなるおそれがある.この問題を解決するため,テストを一つずつ実行するという手段が有る.これを実現するには実行ファイルに--test-thread
フラグを与える.
$cargo test -- --test-threads=1
とすれば,テストを一つずつ実行するようになる.
Showing Function Output
あるテスト関数の中での標準出力は,そのテストが成功するとtest libraryに回収されて,出力されない.例えばprintln!
を中に含むテストが成功すると,シェルからそのprintln!
が出力した値を見ることはできない.一方テストが失敗すれば全ての標準出力とエラーメッセージがシェルから見れる.
例えばlisting 11-10は成功するテストと失敗するテストを持つ.
src/lib.rs listing 11-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!(10, value);
}
fn this_test_will_fail() {
let value = prints_and_returns_10(8);
assert_eq!(5, value);
}
}
shell
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 'assertion failed: `(left == right)` (left: `5`, right: `10`)', src/lib.rs:19
note: Run with `RUST_BACKTRACE=1` for a backtrace.
failures:
tests::this_test_will_fail
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured
error: test failed, to rerun pass '--lib'
たしかにI got the value 8
と,エラー時のみ標準出力が現れている.通過したテストの標準出力を見たい場合,--nocapture
フラグを使う.
$ cargo test -- --nocapture
によって
shell
running 2 tests
I got the value 4
I got the value 8
thread 'tests::this_test_will_fail' panicked at 'assertion failed: `(left == right)` (left: `5`, right: `10`)', src/lib.rs:19
note: Run with `RUST_BACKTRACE=1` for a backtrace.
test tests::this_test_will_pass ... ok
test tests::this_test_will_fail ... FAILED
と,たしかに両方の標準出力がシェルから見れるようになっている.
Running a Subset of Tests by Name
全てのテストを行うと時間がかかりすぎるときは,cargo test
の引数としてどのテストを走らせるかを指定することが出来る.
例えば
src/lib.rs listing 11-11
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_hunder() {
assert_eq!(102, add_two(100));
}
}
listing 11-11のコードは明らかに全てのテストが通過する.
Running Single Tests
cargo test
の引数にテスト関数名を入れることで,そのテスト関数だけを実行できる.
$ cargo test one_hundred
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running target/debug/deps/adder-06a75b4a1f2515e9
running 1 test
test tests::one_hundred ... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured
Ignore Some Tests Unless Specifically Requested
非常に時間がかかるテストを,特に指定した場合以外実行したくないときがある.このときは,テスト関数にtest
attributeに加えてignore
attributeをつける.
src/lib.rs
#[test]
fn it_works() {
assert!(true);
}
#[test]
#[ignore]
fn expensive test() {
// code that takes and hour tu run
}
このようにすれば,ただcargo test
としてもexpensive_test
は実行されない.ignore
した関数も実行したいときには,cargo test -- --ignored
とオプションをつける.
The Organization
テストには大きく分けてunit(単体)テストとintegration(結合)テストが有る.unit testはそれぞれのモジュールを切り離して,private interfacesについてテストする.integration testはpublic interfaceと既存のモジュールのみを使って,外からコードを利用しているのと同じ方法でテストされる対象にアクセスし,テストする
Unit tests
コードのそれぞれの部分を他と切り離し,その部分が予期したとおり動くかを試す.unit testはsrc directoryに,テストされるコードとともに書く.tests
という名前のcfg(test)
annotation モジュールを作って中にテスト関数を列挙するのが慣習である.
The Tests Module and #[cfg(test)]
#[cfg(test)]
annotationはRustに,cargo test
コマンドが実行されたときにのみこのモジュールをコンパイルするように命令する.cargo build
が高速かつ生成される実行ファイルが小さくてすむ.integration testはコードとは別のところに書くので,#[cfg(test)]
を書く必要はない.
新しくライブラリプロジェクトを生成すると,src/lib.rsに
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
}
}
と,予めテストの雛形が自動的に書き込まれている.cfg
とはconfigurationの略であって,以下のコードをtest
に設定している.test
に設定されたコードはcargo test
時のみコンパイルされる.
Testing Private Functions
privateな関数をテストする.
src/lib.rs listing 11-12
pub fn add_two(a: i32) -> i32 { // public
internal_adder(a, 2)
}
fn internal_adder(a: i32, b: i32) -> i32 { // private
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn internal() {
assert_eq!(4, internal_adder(2, 2));
}
}
Privacy Rules
あるアイテムがpublicであるとき,親moduleを通じてそのアイテムにアクセスできる.
あるアイテムがprivateであるとき,そのアイテムがあるmoduleとその子moduleからしかそのアイテムにはアクセスできない.
を思い出すと,tests
はadd_two
やinternal_adder
が定義されたスコープの子moduleだから,internal
はinternal_adder
にアクセスできて,テストできる.