プログラムコードのテストは、開発業界において最も古く、重要なプロセスの一つです。しかし、多くの言語ではテストライブラリが別々に提供され、テストを主コードに統合するのが不透明または不便なことがあります。Rustは最初のバージョンから、モジュールテストを「箱から出してすぐに」サポートするように設計されています。
多くの従来の言語では、テストが別に配置されていたり、特定のフレームワークやビルダーを設定する必要があります。これにより、共同開発が難しくなり、メインロジックとテストの間のリズムがずれるリスクを増大させ、統合テストとモジュールテストの実施が困難になります。
Rustは言語レベルでテストシステムを統合しています:モジュール #[cfg(test)]、ディレクティブ #[test]、およびツール cargo test により、ソースコード内でテストを作成、実行、管理できます。これにより、コードとそれを検証するテストの間の密な結びつきが確保され、テストの起動の容易さとCI/CDプロセスの自動化が保証されます。
コード例:
// tests are automatically compiled and run by `cargo test` #[cfg(test)] mod tests { use super::*; #[test] fn it_adds_two() { assert_eq!(2 + 2, 4); } }
主な特徴:
cargo test)で、すべての発見されたテストが起動します。#[cfg(test)] の利用により、テストをリリースビルドに含めないことができます。テストで不安定なまたはプライベートな関数を使用できますか? それをどうするのですか?
はい、できます。 #[cfg(test)]として宣言されたテストモジュール内では、テストは現在のモジュールの公開APIだけでなく、プライベートAPIも見ることができます。つまり、実装の詳細を直接テストすることができます。ただし、別のモジュールからは公開インターフェースを介してのみアクセス可能です。
例:
fn private_internal(x: i32) -> i32 { x + 1 } #[cfg(test)] mod tests { use super::*; #[test] fn test_private() { assert_eq!(private_internal(41), 42); } }
異なるモジュール間で同じ名前のテストが衝突しますか?
いいえ、テスト名は自モジュール内でのみ可視です。異なるモジュールのテストは、コンパイラがそれらを完全なパス(名前空間)で区別するため、同じ名前を持つことができます。
例:
mod a { #[cfg(test)] mod tests { #[test] fn basic() {} } } mod b { #[cfg(test)] mod tests { #[test] fn basic() {} } }
特定のテストまたはテストセットの一部を実行できますか?
はい、テスト名をフィルタリングすることによって: cargo test テスト名。これは大規模なセットのデバッグに便利です。
例:
cargo test only_this_test
開発者は、プライベート関数にアクセスできない別のプロジェクトにテストを移行しました。メインライブラリの変更により、テストが迅速に遅れ、関連性を失いました。
利点: テストを隔離し、テストモジュールがリリースビルドに偶然含まれるのを避けることができます。
欠点: 同期の喪失、内部詳細のテスト不可、メンテナンス性の低下とテストの非関連性のリスク。
テストは主要なロジックと同じモジュールに書かれ、小さな原子的なテストケースがプライベートおよびパブリックAPIをテストします。初心者はテストを通じてモジュールの作業に迅速に入り込むことができます。
利点: 同期の最大限のサポート、透明性、迅速なローカルテスト、テスト駆動開発に適した。
欠点: ソースファイルの行数の増加、大量のテストによる複雑性の増加の可能性。