はじめに
テストの書き方へようこそ。この実験は、Rust Bookの一部です。LabExでRustのスキルを練習することができます。
この実験では、属性、マクロ、アサーションを使ってRustでテストを書く方法を学びます。
This tutorial is from open-source community. Access the source code
💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください
テストの書き方へようこそ。この実験は、Rust Bookの一部です。LabExでRustのスキルを練習することができます。
この実験では、属性、マクロ、アサーションを使ってRustでテストを書く方法を学びます。
テストは、非テストコードが期待通りに機能していることを検証するRust関数です。テスト関数の本体は通常、これらの3つのアクションを実行します。
これらのアクションを行うためのテストを書くためにRustが提供する機能を見てみましょう。これには、test
属性、いくつかのマクロ、およびshould_panic
属性が含まれます。
最も単純な形で、Rustのテストはtest
属性で注釈付けされた関数です。属性は、Rustコードの一部に関するメタデータです。例として、第5章で構造体に使用したderive
属性があります。関数をテスト関数に変更するには、fn
の前の行に#[test]
を追加します。cargo test
コマンドでテストを実行すると、Rustは注釈付けされた関数を実行し、各テスト関数が合格するか失敗するかを報告するテストランナーバイナリをビルドします。
Cargoで新しいライブラリプロジェクトを作成するたびに、その中にテスト関数を持つテストモジュールが自動的に生成されます。このモジュールは、テストを書くためのテンプレートを提供します。これにより、新しいプロジェクトを始めるたびに正確な構造と構文を調べる必要がなくなります。必要なだけ多くの追加のテスト関数とテストモジュールを追加できます!
実際にコードをテストする前に、テンプレートテストを使ってテストがどのように機能するかのいくつかの側面を調べます。その後、書いたコードを呼び出し、その動作が正しいことをアサートする、現実世界のテストを書きます。
2つの数を加算する新しいライブラリプロジェクトadder
を作成しましょう。
$ cargo new adder --lib
Created library $(adder) project
$ cd adder
adder
ライブラリのsrc/lib.rs
ファイルの内容は、リスト11-1のようになるはずです。
ファイル名: src/lib.rs
#[cfg(test)]
mod tests {
1 #[test]
fn it_works() {
let result = 2 + 2;
2 assert_eq!(result, 4);
}
}
リスト11-1: cargo new
によって自動生成されるテストモジュールと関数
今は、最初の2行を無視して、関数に焦点を当てましょう。#[test]
の注釈[1]に注目してください。この属性は、これがテスト関数であることを示しており、テストランナーはこの関数をテストとして扱うようになっています。tests
モジュールには、共通のシナリオを設定したり、共通の操作を実行したりするための非テスト関数もあるかもしれません。だから、いつもどの関数がテストであるかを示す必要があります。
例の関数本体は、assert_eq!
マクロ[2]を使って、2と2を加算した結果を含むresult
が4に等しいことをアサートしています。このアサーションは、典型的なテストの形式の例となっています。これを実行して、このテストが合格することを確認しましょう。
cargo test
コマンドは、プロジェクト内のすべてのテストを実行します。リスト11-2に示すようになります。
$ cargo test
Compiling adder v0.1.0 (file:///projects/adder)
Finished test [unoptimized + debuginfo] target(s) in 0.57s
Running unittests src/lib.rs (target/debug/deps/adder-
92948b65e88960b4)
1 running 1 test
2 test tests::it_works... ok
3 test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
4 Doc-tests adder
running 0 tests
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
リスト11-2: 自動生成されたテストを実行した結果
Cargoはテストをコンパイルして実行しました。running 1 test
という行が表示されます[1]。次の行は、生成されたテスト関数の名前であるit_works
と、そのテストの実行結果がok
であることが表示されます[2]。全体の要約であるtest result: ok.
[3]は、すべてのテストが合格したことを意味しており、1 passed; 0 failed
と表示される部分は、合格または失敗したテストの数を合算したものです。
特定のインスタンスでテストを実行しないように、テストを無視することができます。これについては、「特定の要求がない限り一部のテストを無視する」で説明します。ここではそのようなことをしていないので、要約には0 ignored
が表示されます。また、cargo test
コマンドに引数を渡して、名前が特定の文字列を含むテストのみを実行することもできます。これを「名前で一部のテストを実行する」で説明します。ここでは、実行するテストをフィルタリングしていないので、要約の末尾には0 filtered out
が表示されます。
0 measured
の統計値は、パフォーマンスを測定するベンチマークテスト用のものです。この記事執筆時点では、ベンチマークテストはnightly Rustでのみ利用可能です。詳細は、https://doc.rust-lang.org/unstable-book/library-features/test.htmlのベンチマークテストに関するドキュメントを参照してください。
テスト出力の次の部分は、Doc-tests adder
[4]から始まり、ドキュメントテストの結果に関するものです。まだドキュメントテストはありませんが、RustはAPIドキュメントに表示されるコード例をコンパイルすることができます。この機能により、ドキュメントとコードを同期させることができます!「ドキュメントコメントをテストとして」で、ドキュメントテストの書き方について説明します。今は、Doc-tests
の出力を無視します。
自分自身のニーズに合わせてテストをカスタマイズし始めましょう。まず、it_works
関数の名前をexploration
などの別の名前に変更します。
ファイル名: src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn exploration() {
let result = 2 + 2;
assert_eq!(result, 4);
}
}
そして、もう一度cargo test
を実行します。出力には今はit_works
の代わりにexploration
が表示されます。
running 1 test
test tests::exploration... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
今度は、もう1つのテストを追加しますが、今回は失敗するテストを作成します!テスト関数内の何かがパニックすると、テストは失敗します。各テストは新しいスレッドで実行され、メインスレッドがテストスレッドが終了したことを検知すると、テストは失敗としてマークされます。第9章では、パニックする最も簡単な方法はpanic!
マクロを呼び出すことであると説明しました。新しいテストをanother
という名前の関数として入力します。すると、src/lib.rs
ファイルはリスト11-3のようになります。
ファイル名: src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn exploration() {
assert_eq!(2 + 2, 4);
}
#[test]
fn another() {
panic!("Make this test fail");
}
}
リスト11-3: panic!
マクロを呼び出すことで失敗する2番目のテストを追加
cargo test
を使ってもう一度テストを実行します。出力はリスト11-4のようになり、exploration
テストが合格し、another
テストが失敗したことが示されます。
running 2 tests
test tests::exploration... ok
1 test tests::another... FAILED
2 failures:
---- tests::another stdout ----
thread 'main' panicked at 'Make this test fail', src/lib.rs:10:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace
3 failures:
tests::another
4 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'
リスト11-4: 1つのテストが合格し、1つのテストが失敗した場合のテスト結果
test tests::another
の行には、ok
の代わりにFAILED
が表示されます[1]。個々の結果と要約の間に、2つの新しいセクションが表示されます。最初のセクション[2]は、各テストの失敗の詳細な理由を表示します。この場合、src/lib.rs
ファイルの10行目でanother
が'Make this test fail'
でパニックしたために失敗したことがわかります。次のセクション[3]は、すべての失敗したテストの名前のみをリストしており、たくさんのテストとたくさんの詳細な失敗したテストの出力がある場合に便利です。失敗したテストの名前を使って、そのテストのみを実行して、より簡単にデバッグすることができます。「テストの実行方法を制御する」で、テストを実行する方法についてもっと詳しく説明します。
要約行は最後に表示されます[4]。全体的なテスト結果はFAILED
です。1つのテストが合格し、1つのテストが失敗しました。
これで、さまざまなシナリオでのテスト結果を見たので、panic!
以外の、テストで役立ついくつかのマクロを見てみましょう。
assert!
マクロを使った結果のチェック標準ライブラリによって提供される assert!
マクロは、テスト内のある条件が true
であることを確認したい場合に便利です。assert!
マクロには、ブール値に評価される引数を渡します。値が true
の場合、何も起こらず、テストは合格します。値が false
の場合、assert!
マクロは panic!
を呼び出してテストを失敗させます。assert!
マクロを使うことで、コードが意図通りに機能していることを確認できます。
リスト5-15では、Rectangle
構造体と can_hold
メソッドを使用しました。これらは、リスト11-5に再掲してあります。このコードを src/lib.rs
ファイルに入れて、assert!
マクロを使ってそれに対するいくつかのテストを書きましょう。
ファイル名: src/lib.rs
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
}
リスト11-5: 第5章の Rectangle
構造体とその can_hold
メソッドの使用
can_hold
メソッドはブール値を返すので、assert!
マクロに最適なケースです。リスト11-6では、幅が8で高さが7の Rectangle
インスタンスを作成し、幅が5で高さが1の別の Rectangle
インスタンスを保持できることをアサートすることで、can_hold
メソッドをテストします。
ファイル名: src/lib.rs
#[cfg(test)]
mod tests {
1 use super::*;
#[test]
2 fn larger_can_hold_smaller() {
3 let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
4 assert!(larger.can_hold(&smaller));
}
}
リスト11-6: 大きな四角形が小さな四角形を実際に保持できるかどうかをチェックする can_hold
のテスト
tests
モジュールの中に新しい行 use super::*;
を追加しています[1]。tests
モジュールは、「モジュールツリー内のアイテムを参照するためのパス」で説明した通常の可視性ルールに従う通常のモジュールです。tests
モジュールは内部モジュールなので、テスト対象のコードを外部モジュールから内部モジュールのスコープに持ち込む必要があります。ここではグロブを使っているので、外部モジュールで定義したものはすべてこの tests
モジュールで利用できます。
テストの名前を larger_can_hold_smaller
にしています[2]。そして、必要な2つの Rectangle
インスタンスを作成しています[3]。その後、assert!
マクロを呼び出し、larger.can_hold(&smaller)
の呼び出し結果を渡しています[4]。この式は true
を返すはずなので、テストは合格するはずです。確認してみましょう!
running 1 test
test tests::larger_can_hold_smaller... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
合格しました!もう1つのテストを追加しましょう。今度は、小さな四角形が大きな四角形を保持できないことをアサートします。
ファイル名: src/lib.rs
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn larger_can_hold_smaller() {
--snip--
}
#[test]
fn smaller_cannot_hold_larger() {
let larger = Rectangle {
width: 8,
height: 7,
};
let smaller = Rectangle {
width: 5,
height: 1,
};
assert!(!smaller.can_hold(&larger));
}
}
この場合、can_hold
関数の正しい結果は false
なので、assert!
マクロに渡す前にその結果を否定する必要があります。その結果、can_hold
が false
を返す場合、テストは合格します。
running 2 tests
test tests::larger_can_hold_smaller... ok
test tests::smaller_cannot_hold_larger... ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
2つのテストが合格しました!では、コードにバグを入れた場合、テスト結果がどうなるか見てみましょう。can_hold
メソッドの実装を変更して、幅を比較する際の大なり記号を小なり記号に置き換えます。
--snip--
impl Rectangle {
fn can_hold(&self, other: &Rectangle) -> bool {
self.width < other.width && self.height > other.height
}
}
今、テストを実行すると次のような結果になります。
running 2 tests
test tests::smaller_cannot_hold_larger... ok
test tests::larger_can_hold_smaller... FAILED
failures:
---- tests::larger_can_hold_smaller stdout ----
thread'main' panicked at 'assertion failed:
larger.can_hold(&smaller)', src/lib.rs:28:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace
failures:
tests::larger_can_hold_smaller
test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
テストがバグを検出しました!larger.width
が8で smaller.width
が5なので、can_hold
での幅の比較は今では false
を返します。8は5未満ではありません。
assert_eq!
と assert_ne!
マクロを使った等価性のテスト機能を検証する一般的な方法は、テスト対象のコードの結果と、コードが返すはずの値との等価性をテストすることです。これは、assert!
マクロを使って ==
演算子を使った式を渡すことで行うことができます。しかし、このような一般的なテストのために、標準ライブラリには2つのマクロ assert_eq!
と assert_ne!
が用意されており、これらを使うことでもっと便利にこのテストを行うことができます。これらのマクロは、それぞれ2つの引数を等価性または非等価性で比較します。また、アサーションが失敗した場合、2つの値を表示します。これにより、テストが失敗した理由がわかりやすくなります。逆に、assert!
マクロは、==
式に対して false
の値を取得したことを示すだけで、false
の値に至る値を表示しません。
リスト11-7では、add_two
という名前の関数を書きます。この関数は、引数に2を加えます。そして、assert_eq!
マクロを使ってこの関数をテストします。
ファイル名: src/lib.rs
pub fn add_two(a: i32) -> i32 {
a + 2
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_adds_two() {
assert_eq!(4, add_two(2));
}
}
リスト11-7: assert_eq!
マクロを使った add_two
関数のテスト
合格することを確認しましょう!
running 1 test
test tests::it_adds_two... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
assert_eq!
に引数として 4
を渡しています。これは、add_two(2)
の呼び出し結果と等しいです。このテストの行は test tests::it_adds_two... ok
で、ok
のテキストが表示されることで、テストが合格したことがわかります!
コードにバグを入れて、assert_eq!
が失敗したときの様子を見てみましょう。add_two
関数の実装を変更して、代わりに3を加えるようにします。
pub fn add_two(a: i32) -> i32 {
a + 3
}
もう一度テストを実行します。
running 1 test
test tests::it_adds_two... FAILED
failures:
---- tests::it_adds_two stdout ----
1 thread'main' panicked at 'assertion failed: `(left == right)`
2 left: `4`,
3 right: `5`', src/lib.rs:11:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace
failures:
tests::it_adds_two
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
テストがバグを検出しました!it_adds_two
テストが失敗し、メッセージには、失敗したアサーションが assertion failed:
(left == right)`\[1\] であり、
left\[2\] と
right\[3\] の値が何であるかが示されています。このメッセージはデバッグを始めるのに役立ちます。
left引数は
4 でしたが、
right引数である
add_two(2)の結果は
5` でした。たくさんのテストを行っている場合、これが特に役立つことが想像できます。
いくつかの言語やテストフレームワークでは、等価性アサーション関数のパラメータは expected
と actual
と呼ばれ、引数を指定する順序が重要です。しかし、Rustでは、それらは left
と right
と呼ばれ、期待する値とコードが生成する値を指定する順序は重要ではありません。このテストのアサーションを assert_eq!(add_two(2), 4)
のように書くこともできます。これにより、assertion failed:
(left == right)` と表示される同じ失敗メッセージが表示されます。
assert_ne!
マクロは、与えられた2つの値が等しくなければ合格し、等しければ失敗します。このマクロは、値が何になるかはわからないが、何になるべきではないことは確かである場合に最も役立ちます。たとえば、入力を必ず何らかの方法で変更する関数をテストしている場合、入力がどのように変更されるかはテストを実行する曜日に依存する場合、関数の出力が入力と等しくないことをアサートするのが最善策かもしれません。
表面下では、assert_eq!
と assert_ne!
マクロはそれぞれ ==
と !=
演算子を使用します。アサーションが失敗した場合、これらのマクロはデバッグフォーマットを使って引数を表示します。これは、比較される値が PartialEq
と Debug
トレイトを実装していることを意味します。すべてのプリミティブ型とほとんどの標準ライブラリ型はこれらのトレイトを実装しています。自分で定義する構造体や列挙型の場合、それらの型の等価性をアサートするには PartialEq
を実装する必要があります。また、アサーションが失敗したときに値を表示するには Debug
を実装する必要があります。両方のトレイトは派生可能なトレイトであるため、リスト5-12で述べたように、これは通常、構造体や列挙型の定義に #[derive(PartialEq, Debug)]
注釈を追加するだけで簡単に行えます。これらの派生可能なトレイトやその他のトレイトに関する詳細については、付録Cを参照してください。
assert!
、assert_eq!
、および assert_ne!
マクロには、失敗メッセージとともに表示するカスタムメッセージを追加することができます。これは、任意の引数として指定します。必要な引数の後に指定された任意の引数は、format!
マクロ(「+演算子またはformat!マクロを使った連結」で説明)に渡されます。これにより、{}
プレースホルダとそれに入れる値を含む書式文字列が渡せます。カスタムメッセージは、アサーションの意味を文書化するのに役立ちます。テストが失敗したときに、コードの問題が何であるかをよりよく把握することができます。
たとえば、名前で人を迎える関数があり、関数に渡した名前が出力に表示されることをテストしたいとしましょう。
ファイル名: src/lib.rs
pub fn greeting(name: &str) -> String {
format!("Hello {name}!")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(result.contains("Carol"));
}
}
このプログラムの要件はまだ合意されておらず、挨拶の冒頭の Hello
のテキストが変更されると思われます。要件が変更されたときにテストを更新する必要がないように、greeting
関数から返される値と厳密に等しいことをチェックする代わりに、出力に入力パラメータのテキストが含まれていることをアサートすることにしました。
では、このコードにバグを入れて、greeting
を変更して name
を除外して、デフォルトのテスト失敗がどのように見えるか見てみましょう。
pub fn greeting(name: &str) -> String {
String::from("Hello!")
}
このテストを実行すると、次のような結果になります。
running 1 test
test tests::greeting_contains_name... FAILED
failures:
---- tests::greeting_contains_name stdout ----
thread'main' panicked at 'assertion failed:
result.contains(\"Carol\")', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace
failures:
tests::greeting_contains_name
この結果は、アサーションが失敗したことと、アサーションがある行を示しています。より役立つ失敗メッセージは、greeting
関数からの値を表示するでしょう。greeting
関数から得た実際の値で埋められたプレースホルダを持つ書式文字列で構成されるカスタムの失敗メッセージを追加しましょう。
#[test]
fn greeting_contains_name() {
let result = greeting("Carol");
assert!(
result.contains("Carol"),
"Greeting did not contain name, value was `{result}`"
);
}
今、テストを実行すると、より情報が豊富なエラーメッセージが表示されます。
---- tests::greeting_contains_name stdout ----
thread'main' panicked at 'Greeting did not contain name, value
was `Hello!`', src/lib.rs:12:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace
テスト出力に実際に得た値が表示されるので、期待したことではなく何が起こったかをデバッグするのに役立ちます。
should_panic
を使ったパニックのチェック戻り値をチェックするだけでなく、コードが期待通りにエラー条件を処理することをチェックすることも重要です。たとえば、リスト9-13で作成した Guess
型を考えてみましょう。Guess
を使用する他のコードは、Guess
インスタンスが1から100の間の値のみを含むことを保証に依存しています。そこで、その範囲外の値で Guess
インスタンスを作成しようとするとパニックすることを確認するテストを書くことができます。
これは、テスト関数に should_panic
属性を追加することで行います。関数内のコードがパニックする場合、テストは合格します。関数内のコードがパニックしない場合、テストは失敗します。
リスト11-8は、Guess::new
のエラー条件が期待通りに発生することを確認するテストを示しています。
// src/lib.rs
pub struct Guess {
value: i32,
}
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 || value > 100 {
panic!(
"Guess value must be between 1 and 100, got {}.",
value
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn greater_than_100() {
Guess::new(200);
}
}
リスト11-8: 条件がパニックを引き起こすことをテストする
#[should_panic]
属性を、#[test]
属性の後で、それが適用されるテスト関数の前に配置します。このテストが合格したときの結果を見てみましょう。
running 1 test
test tests::greater_than_100 - should panic... ok
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
うまくいっているようです!では、コードにバグを入れて、new
関数が値が100を超えた場合にパニックする条件を削除してみましょう。
// src/lib.rs
--snip--
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be between 1 and 100, got {}.",
value
);
}
Guess { value }
}
}
リスト11-8のテストを実行すると、失敗します。
running 1 test
test tests::greater_than_100 - should panic... FAILED
failures:
---- tests::greater_than_100 stdout ----
note: test did not panic as expected
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s
この場合、あまり役立たないメッセージが表示されますが、テスト関数を見ると、#[should_panic]
で注釈付けされていることがわかります。得られた失敗は、テスト関数内のコードがパニックを引き起こさなかったことを意味します。
should_panic
を使用するテストは不正確である可能性があります。should_panic
テストは、期待した理由とは別の理由でテストがパニックした場合でも合格します。should_panic
テストをより正確にするには、should_panic
属性にオプションの expected
パラメータを追加することができます。テストハーネスは、失敗メッセージに提供されたテキストが含まれていることを確認します。たとえば、リスト11-9の Guess
の修正コードを考えてみましょう。ここでは、new
関数は値が小さすぎるか大きすぎるかに応じて、異なるメッセージでパニックします。
// src/lib.rs
--snip--
impl Guess {
pub fn new(value: i32) -> Guess {
if value < 1 {
panic!(
"Guess value must be greater than or equal to 1, got {}.",
value
);
} else if value > 100 {
panic!(
"Guess value must be less than or equal to 100, got {}.",
value
);
}
Guess { value }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "less than or equal to 100")]
fn greater_than_100() {
Guess::new(200);
}
}
リスト11-9: 指定された部分文字列を含むパニックメッセージでの panic!
のテスト
このテストは合格します。なぜなら、should_panic
属性の expected
パラメータに入れた値は、Guess::new
関数がパニックするメッセージの部分文字列だからです。この場合、期待する完全なパニックメッセージは Guess value must be less than or equal to 100, got 200
である可能性があります。どのようなパニックメッセージを指定するかは、パニックメッセージのどれが一意または動的であり、テストをどの程度正確にしたいかに依存します。この場合、パニックメッセージの部分文字列だけでも、テスト関数内のコードが else if value > 100
のケースを実行することを保証するのに十分です。
expected
メッセージ付きの should_panic
テストが失敗したときに何が起こるかを見るために、コードにバグを入れて、if value < 1
と else if value > 100
のブロックの本体を入れ替えましょう。
// src/lib.rs
--snip--
if value < 1 {
panic!(
"Guess value must be less than or equal to 100, got {}.",
value
);
} else if value > 100 {
panic!(
"Guess value must be greater than or equal to 1, got {}.",
value
);
}
--snip--
今度は、should_panic
テストを実行すると、失敗します。
running 1 test
test tests::greater_than_100 - should panic... FAILED
failures:
---- tests::greater_than_100 stdout ----
thread'main' panicked at 'Guess value must be greater than or equal to 1, got
200.', src/lib.rs:13:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
note: panic did not contain expected string
panic message: `"Guess value must be greater than or equal to 1, got
200."`,
expected substring: `"less than or equal to 100"`
failures:
tests::greater_than_100
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out;
finished in 0.00s
失敗メッセージは、このテストが確かに期待通りにパニックしたことを示していますが、パニックメッセージには期待される文字列 'Guess value must be less than or equal to 100'
が含まれていません。この場合に得られたパニックメッセージは Guess value must be greater than or equal to 1, got 200
でした。これで、バグがどこにあるかを見つけ始めることができます!
Result<T, E>
の使用これまでのテストは、失敗するとすべてパニックします。また、Result<T, E>
を使ったテストも書くことができます!ここに、リスト11-1のテストを書き直して、Result<T, E>
を使ってパニックする代わりに Err
を返すようにしたものです。
ファイル名: src/lib.rs
#[cfg(test)]
mod tests {
#[test]
fn it_works() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("two plus two does not equal four"))
}
}
}
it_works
関数の返り値の型が今は Result<(), String>
になっています。関数の本体では、assert_eq!
マクロを呼び出す代わりに、テストが合格したときに Ok(())
を返し、テストが失敗したときに String
を含む Err
を返します。
テストを書いて Result<T, E>
を返すようにすることで、テストの本体で疑問符演算子を使うことができます。これは、その中のどの操作が Err
変数を返した場合にテストが失敗するようなテストを書く便利な方法です。
Result<T, E>
を使ったテストには、#[should_panic]
注釈を使うことはできません。操作が Err
変数を返すことをアサートするには、Result<T, E>
値に疑問符演算子を使わないでください。代わりに、assert!(value.is_err())
を使ってください。
これで、いくつかのテストの書き方を知ったので、テストを実行したときに何が起こっているか見て、cargo test
で使えるさまざまなオプションを探ってみましょう。
おめでとうございます!「テストの書き方」の実験を終えました。さらに実験を通じて技術力を向上させるために、LabExでさまざまな実験を行ってみましょう。