テスト
Rustの組み込みテスト機能を学びます。
単体テスト
基本的なテスト
#![allow(unused)]
fn main() {
fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
}
}
実行:
cargo test
アサーションマクロ
#![allow(unused)]
fn main() {
#[test]
fn test_assertions() {
// 等値チェック
assert_eq!(1 + 1, 2);
// 不等値チェック
assert_ne!(1 + 1, 3);
// 条件チェック
assert!(true);
assert!(!false);
// カスタムメッセージ
assert_eq!(1 + 1, 2, "1 + 1 は 2 のはず");
}
}
パニックのテスト
#![allow(unused)]
fn main() {
fn divide(a: i32, b: i32) -> i32 {
if b == 0 {
panic!("ゼロ除算!");
}
a / b
}
#[test]
#[should_panic]
fn test_divide_by_zero() {
divide(10, 0);
}
#[test]
#[should_panic(expected = "ゼロ除算")]
fn test_divide_by_zero_message() {
divide(10, 0);
}
}
Result を返すテスト
#![allow(unused)]
fn main() {
#[test]
fn test_with_result() -> Result<(), String> {
if 2 + 2 == 4 {
Ok(())
} else {
Err(String::from("計算が間違っています"))
}
}
}
テストの構成
テストモジュール
#![allow(unused)]
fn main() {
// src/lib.rs
pub fn public_function() -> i32 {
private_function() + 1
}
fn private_function() -> i32 {
42
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_public() {
assert_eq!(public_function(), 43);
}
#[test]
fn test_private() {
// 同じモジュール内なのでプライベート関数もテスト可能
assert_eq!(private_function(), 42);
}
}
}
テストのフィルタリング
# すべてのテスト
cargo test
# 特定のテストのみ
cargo test test_add
# 特定のモジュールのテスト
cargo test tests::
# 無視されたテストを実行
cargo test -- --ignored
テストの無視
#![allow(unused)]
fn main() {
#[test]
#[ignore]
fn expensive_test() {
// 時間のかかるテスト
}
}
統合テスト
プロジェクト全体をテストします。
my_project/
├── src/
│ └── lib.rs
└── tests/
└── integration_test.rs
tests/integration_test.rs
#![allow(unused)]
fn main() {
use my_project;
#[test]
fn test_integration() {
assert_eq!(my_project::public_function(), 43);
}
}
テストのベストプラクティス
1. AAAパターン
#![allow(unused)]
fn main() {
#[test]
fn test_user_creation() {
// Arrange(準備)
let name = "太郎";
let age = 25;
// Act(実行)
let user = User::new(name.to_string(), age);
// Assert(検証)
assert_eq!(user.name, "太郎");
assert_eq!(user.age, 25);
}
}
2. 境界値テスト
#![allow(unused)]
fn main() {
#[test]
fn test_is_adult() {
assert!(!is_adult(17)); // 境界の下
assert!(is_adult(18)); // 境界
assert!(is_adult(19)); // 境界の上
}
}
3. エラーケースのテスト
#![allow(unused)]
fn main() {
#[test]
fn test_parse_error() {
let result = parse_number("not a number");
assert!(result.is_err());
}
#[test]
fn test_parse_success() {
let result = parse_number("42");
assert_eq!(result.unwrap(), 42);
}
}
テストヘルパー
共通のセットアップ
#![allow(unused)]
fn main() {
#[cfg(test)]
mod tests {
use super::*;
fn setup() -> User {
User::new("テスト太郎".to_string(), 20)
}
#[test]
fn test_user_name() {
let user = setup();
assert_eq!(user.name, "テスト太郎");
}
#[test]
fn test_user_age() {
let user = setup();
assert_eq!(user.age, 20);
}
}
}
テストカバレッジ
# cargo-tarpaulinをインストール
cargo install cargo-tarpaulin
# カバレッジレポート
cargo tarpaulin
まとめ
| 機能 | 説明 |
|---|---|
#[test] | テスト関数を示す |
assert! | 条件がtrueか検証 |
assert_eq! | 等値を検証 |
assert_ne! | 不等値を検証 |
#[should_panic] | パニックを期待 |
#[ignore] | テストを無視 |
#[cfg(test)] | テスト時のみコンパイル |
確認テスト
Q1. `assert_eq!(a, b)`と`assert_ne!(a, b)`の違いは?
正解: A) `assert_eq!`は2つの値が等しいことを、`assert_ne!`は等しくないことを検証します。
Q2. `#[should_panic]`の用途は?
正解: B) `#[should_panic]`をつけたテストは、パニックが発生すると成功、発生しないと失敗します。エラーケースのテストに使います。
Q3. 以下のテストの結果は?#[test] fn test() { assert_eq!(2 + 2, 5); }
正解: B) 2 + 2 = 4 であり、5と等しくないため、テストは失敗します。
Q4. `assert!(is_even(3))`が失敗する理由は?(is_evenは偶数でtrueを返す関数)
正解: C) 3は奇数なので`is_even(3)`は`false`を返します。`assert!`は引数が`false`のとき失敗するため、テストは失敗します。
Q5. 統合テストを配置する正しいディレクトリは?
正解: A) 統合テストはプロジェクトルートの`tests/`ディレクトリに配置します。各ファイルが独立したクレートとしてコンパイルされます。
次のドキュメント: 04_documentation.md