Rustは元々、信頼性と安全性に重点を置いて設計されているため、モジュールテストは言語のエコシステムの不可欠な部分となっています。テストは言語及びツールチェーンのレベル(cargo test)で統合されており、開発プロセスの有機的な一部となっています。
問題の歴史
従来のエコシステム(例えばC/C++、Python、Java)では、テストはプログラムから独立して存在していました。Rustでは、テストはコードの一部であり、完全なモジュールとしてコンパイルされ検証されます。このようなシナジーは、言語構文やコンパイラの特性によって得られています。
問題
正しいテストがなければ、重要な機能の信頼性を保証することはできません。複雑でモジュール化されたプロジェクトでは、テストをどのように便利に整理し、他のモジュールの状態に依存させず、プロジェクトの構造を複雑にしないかという問題がしばしば生じます。
解決策
Rustでは、テストは元のソースファイル内(#[cfg(test)]を使用)に配置するか、統合テスト用の別のフォルダーtestsに配置します。各モジュールに対してプライベートテストを追加でき、プライベートAPIにアクセス可能です。
コードの例:
pub fn add(a: i32, b: i32) -> i32 { a + b } #[cfg(test)] mod tests { use super::*; #[test] fn test_add_positive() { assert_eq!(add(2, 3), 5); } #[test] fn test_add_negative() { assert_eq!(add(-1, -3), -4); } }
重要な特徴:
テストモジュールのuse super::*;は何のために必要ですか?
テストが現在のモジュールの関数や構造体(プライベートなものを含む)にアクセスできるようにするため、テストでは通常use super::*;を使用します。
#[test]は非同期にできますか?
デフォルトでは#[test]はasyncをサポートしていませんが、外部のクレート(例えばtokioやasync-std)を使用することでasync #[test]を作成できます。
コードの例:
#[tokio::test] async fn test_async_add() { assert_eq!(add(2, 2).await, 4); }
テストはグローバルな状態を変更できますか?それを回避する方法は?
Rustのテストはデフォルトで並行に実行されるため、共有されたグローバル状態(static mut)を同期なしで使用することはできず、そうしないとレースコンディションが発生します。
すべてのテストが1つのグローバル変数を使用します:
static mut COUNTER: u32 = 0; #[test] fn test_inc() { unsafe { COUNTER += 1; } }
利点:
欠点:
各テストは隔離されており、ローカル変数とモックオブジェクトを使用します。
利点:
欠点: