1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rustのテストの書き方【#[test]から統合テストまで丁寧に解説】

1
Posted at

Rustを学び始めてテストはどう書くの?と思ったことはありませんか。
この記事では、ユニットテスト・統合テスト・ドクテストを、コード例を交えながら丁寧に解説します。


なぜRustのテストを書くのか

Rustのコンパイラは型チェックや所有権チェックで多くのバグを防ぎますが、ロジックの正しさは保証しません。

fn add(a: i32, b: i32) -> i32 {
    a - b  // バグ!本当は a + b のはずだがコンパイルは通る
}

このような実装ミスはコンパイラでは検出できません。テストを書くことで、コンパイラが保証できないビジネスロジックの正しさを担保できます。

Rustのテストは標準ツールチェーンに含まれており、外部ライブラリ・設定ファイル不要で始められます。


テストの3種類

Rustには大きく3種類のテストがあります。

種類 場所 用途
ユニットテスト src/ 内の #[cfg(test)] ブロック 関数・モジュール単位の検証
統合テスト tests/ ディレクトリ 複数モジュールを組み合わせた動作検証
ドクテスト ドキュメントコメント内 ドキュメントとコードの動作を同時に保証

まずはユニットテストから見ていきましょう。


ユニットテスト

#[test] アトリビュート

テスト関数には #[test] を付けるだけです。

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);
    }
}

ポイントは2つあります。

#[cfg(test)] はこのブロックを cargo test 時のみコンパイルする指定です。本番バイナリにテストコードが混入しません。

use super::* は親モジュール(テストモジュールの外)の関数を使えるようにするインポートです。tests モジュールは add 関数の外側にあるため、これがないと add を呼び出せません。

テストの実行

cargo test              # すべてのテストを実行
cargo test test_add     # 名前にマッチするテストだけ実行

実行すると以下のような出力が得られます。

running 1 test
test tests::test_add ... ok

test result: ok. 1 passed; 0 failed

アサーションマクロ

テスト内では専用のマクロで期待値を確認します。

マクロ 意味
assert!(条件) 条件が true であることを確認する
assert_eq!(a, b) a == b であることを確認する
assert_ne!(a, b) a != b であることを確認する
#[test]
fn test_assertions() {
    let result = add(2, 3);

    assert!(result > 0);       // 0より大きい
    assert_eq!(result, 5);     // 5と等しい
    assert_ne!(result, 0);     // 0ではない
}

assert_eq! は失敗したとき左右の値を両方表示してくれるため、assert! より原因がわかりやすいです。

// assert_eq! が失敗した場合の出力例
thread 'tests::test_add' panicked at 'assertion `left == right` failed
  left: 4
 right: 5'

カスタムメッセージ

第3引数にメッセージを追加することもできます。

#[test]
fn test_with_message() {
    let result = add(2, 3);
    assert_eq!(result, 5, "add(2, 3) の結果が想定と異なります: {}", result);
}

テストが複数あって失敗箇所が特定しにくいときに役立ちます。


#[should_panic]:パニックを期待するテスト

関数がエラー時にパニックする設計になっている場合、そのパニックが確かに起きることをテストできます。

fn divide(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("ゼロ除算はできません");
    }
    a / b
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    #[should_panic(expected = "ゼロ除算")]
    fn test_divide_by_zero() {
        divide(10, 0);
    }
}

#[should_panic] だけでもテストは通りますが、expected でパニックメッセージを部分一致で確認することを推奨します。

// ❌ expected なし:意図しないパニックでも通ってしまう
#[test]
#[should_panic]
fn vague_test() {
    divide(10, 0);
}

// ✅ expected あり:このメッセージを含むパニックのみ通る
#[test]
#[should_panic(expected = "ゼロ除算")]
fn precise_test() {
    divide(10, 0);
}

Result を返すテスト

panic! の代わりに Result<(), E> を返す書き方もあります。

#[test]
fn test_with_result() -> Result<(), String> {
    let result = add(2, 3);
    if result != 5 {
        return Err(format!("期待値: 5, 実際: {}", result));
    }
    Ok(())
}

? 演算子が使えるので、Result を返す関数を連鎖させるテストに便利です。

use std::num::ParseIntError;

fn parse_and_double(s: &str) -> Result<i32, ParseIntError> {
    let n: i32 = s.parse()?;
    Ok(n * 2)
}

#[test]
fn test_parse_and_double() -> Result<(), ParseIntError> {
    let result = parse_and_double("5")?;  // ? でエラーをそのまま伝播できる
    assert_eq!(result, 10);
    Ok(())
}

統合テスト

tests/ ディレクトリにファイルを置くと統合テストになります。

my_project/
├── src/
│   └── lib.rs
└── tests/
    └── integration_test.rs
// tests/integration_test.rs
use my_project::add;  // クレート名でインポート

#[test]
fn test_add_integration() {
    assert_eq!(add(10, 20), 30);
}

ユニットテストとの違いは可視性です。

  • ユニットテスト(src/ 内): private 関数も直接テスト可能
  • 統合テスト(tests/ 内): pub な関数のみ使える。外部ユーザーと同じ視点でテストできる

ドクテスト

/// ドキュメントコメントに書いたコード例は、cargo test で自動的にテストされます。

/// 2つの整数を足し合わせる。
///
/// # Examples
///
/// ```
/// use my_project::add;
/// assert_eq!(add(2, 3), 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

ドキュメントを更新したのに実装を直し忘れた、という事故をコンパイル時に検出できます。


まとめ

種類 書き方 向いている用途
ユニットテスト #[test] + #[cfg(test)] mod tests 関数単位の細かい検証
統合テスト tests/ ディレクトリ 公開APIを外部視点で検証
ドクテスト /// コメント内のコード例 ドキュメントと動作の一致を保証
#[should_panic] #[test] と組み合わせる パニックが起きることを検証
Result テスト -> Result<(), E> を返す ? でエラー処理を簡潔に書く

cargo test の一発で3種類すべてが実行されます。外部ライブラリ・設定ファイル不要でここまでできるのがRustの強みです。


テストの書き方をさらに深く学びたい方へ

テストの考え方はどの言語でも共通しています。Rustと並んでバックエンド・CLIで人気のGo言語でテストを体系的に学びたい方にはこちらの講座がおすすめです。

Go言語のテストの書き方【入門】30問ドリルで使えるスキルを完全習得

30問の演習ドリル形式で、Go言語のテスト駆動開発を基礎から実践まで体系的に習得できます。

🎫 現在半額クーポン配布中(有効期限:2026年6月22日)

👉 講座を見る(クーポン自動適用)

講座まとめ

その他のWeb・AI・プログラミング講座は以下でまとめて確認できます。

👉 講座一覧サイト
👉 Udemyプロフィール

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?