Skip to main content Skills Marketplace Descubre y explora habilidades de IA creadas por la comunidad.
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Copiar promptMostrar detalles del prompt Un comando directo omite el prompt de revisión. Revisa el origen antes de ejecutarlo.
npx skills add https://github.com/affaan-m/ECC --skill rust-testingEl comando permanece en una sola línea. Desplázate horizontalmente para revisarlo antes de copiarlo.
¿Prefieres una copia local? Descarga los archivos que SkillsMP tiene disponibles ahora.
Descargar Zip Descargando... Ocupaciones relacionadas SOC
Basado en la clasificación ocupacional SOC
name rust-testing description 単体テスト、統合テスト、非同期テスト、プロパティベーステスト、モック、カバレッジを含むRustテストパターン。TDD方法論に従う。 origin ECC
Rust テストパターン
TDD方法論に従って信頼性が高く保守しやすいテストを書くための包括的なRustテストパターン。
使用場面
新しいRustの関数、メソッド、またはトレイトを書く場合
既存のコードにテストカバレッジを追加する場合
パフォーマンスクリティカルなコードのベンチマークを作成する場合
入力検証にプロパティベーステストを実装する場合
RustプロジェクトでTDDワークフローに従う場合
動作原理
ターゲットコードを特定する — テストする関数、トレイト、またはモジュールを見つける
テストを書く — #[cfg(test)] モジュール内で #[test] を使用、rstest でパラメータ化テスト、または proptest でプロパティベーステスト
依存関係をモックする — mockall を使用してテスト対象のユニットを分離する
テストを実行する (RED) — テストが期待通りに失敗することを確認する
実装する (GREEN) — テストを通過するための最小限のコードを書く
リファクタリングする — テストを通過したまま、コードを改善する
カバレッジを確認する — cargo-llvm-cov を使用し、80%以上を目標にする
RustのTDDワークフロー
RED-GREEN-REFACTOR サイクル
RED → まず失敗するテストを書く
GREEN → テストを通過する最小限のコードを書く
REFACTOR → テストを通過したままコードをリファクタリングする
REPEAT → 次の要件に進む
Rustでの段階的TDD
pub fn add (a: i32 , b: i32 ) -> { todo!() }
tests {
super::*;
() { ( ( , ), ); }
}
i32
#[cfg(test)]
mod
use
#[test]
fn
test_add
assert_eq!
add
2
3
5
pub fn add (a: i32 , b: i32 ) -> i32 { a + b }
単体テスト
モジュールレベルのテスト整理
pub struct User {
pub name: String ,
pub email: String ,
}
impl User {
pub fn new (name: impl Into <String >, email: impl Into <String >) -> Result <Self , String > {
let email = email.into ();
if !email.contains ('@' ) {
return Err (format! ("invalid email: {email}" ));
}
Ok (Self { name: name.into (), email })
}
pub fn display_name (&self ) -> &str {
&self .name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn creates_user_with_valid_email () {
let user = User::new ("Alice" , "alice@example.com" ).unwrap ();
assert_eq! (user.display_name (), "Alice" );
assert_eq! (user.email, "alice@example.com" );
}
#[test]
fn rejects_invalid_email () {
let result = User::new ("Bob" , "not-an-email" );
assert! (result.is_err ());
assert! (result.unwrap_err ().contains ("invalid email" ));
}
}
アサーションマクロ assert_eq! (2 + 2 , 4 );
assert_ne! (2 + 2 , 5 );
assert! (vec! [1 , 2 , 3 ].contains (&2 ));
assert_eq! (value, 42 , "expected 42 but got {value}" );
assert! ((0.1_f64 + 0.2 - 0.3 ).abs () < f64 ::EPSILON);
エラーとパニックのテスト
Result の戻り値のテスト#[test]
fn parse_returns_error_for_invalid_input () {
let result = parse_config ("}{invalid" );
assert! (result.is_err ());
let err = result.unwrap_err ();
assert! (matches!(err, ConfigError::ParseError (_)));
}
#[test]
fn parse_succeeds_for_valid_input () -> Result <(), Box <dyn std::error::Error>> {
let config = parse_config (r#"{"port": 8080}"# )?;
assert_eq! (config.port, 8080 );
Ok (())
}
パニックのテスト #[test]
#[should_panic]
fn panics_on_empty_input () {
process (&[]);
}
#[test]
#[should_panic(expected = "index out of bounds" )]
fn panics_with_specific_message () {
let v : Vec <i32 > = vec! [];
let _ = v[0 ];
}
統合テスト
ファイル構造 my_crate/
├── src/
│ └── lib.rs
├── tests/ # 統合テスト
│ ├── api_test.rs # 各ファイルが独立したテストバイナリ
│ ├── db_test.rs
│ └── common/ # 共有テストユーティリティ
│ └── mod.rs
統合テストの書き方
use my_crate::{App, Config};
#[test]
fn full_request_lifecycle () {
let config = Config::test_default ();
let app = App::new (config);
let response = app.handle_request ("/health" );
assert_eq! (response.status, 200 );
assert_eq! (response.body, "OK" );
}
非同期テスト
Tokioの使用 #[tokio::test]
async fn fetches_data_successfully () {
let client = TestClient::new ().await ;
let result = client.get ("/data" ).await ;
assert! (result.is_ok ());
assert_eq! (result.unwrap ().items.len (), 3 );
}
#[tokio::test]
async fn handles_timeout () {
use std::time::Duration;
let result = tokio::time::timeout (
Duration::from_millis (100 ),
slow_operation (),
).await ;
assert! (result.is_err (), "should have timed out" );
}
テスト整理パターン
rstest を使用したパラメータ化テストuse rstest::{rstest, fixture};
#[rstest]
#[case("hello" , 5)]
#[case("" , 0)]
#[case("rust" , 4)]
fn test_string_length (#[case] input: &str , #[case] expected: usize ) {
assert_eq! (input.len (), expected);
}
#[fixture]
fn test_db () -> TestDb {
TestDb::new_in_memory ()
}
#[rstest]
fn test_insert (test_db: TestDb) {
test_db.insert ("key" , "value" );
assert_eq! (test_db.get ("key" ), Some ("value" .into ()));
}
テストヘルパー関数 #[cfg(test)]
mod tests {
use super::*;
fn make_user (name: &str ) -> User {
User::new (name, &format! ("{name}@test.com" )).unwrap ()
}
#[test]
fn user_display () {
let user = make_user ("alice" );
assert_eq! (user.display_name (), "alice" );
}
}
proptest を使用したプロパティベーステスト
基本的なプロパティテスト use proptest::prelude::*;
proptest! {
#[test]
fn encode_decode_roundtrip (input in ".*" ) {
let encoded = encode (&input);
let decoded = decode (&encoded).unwrap ();
assert_eq! (input, decoded);
}
#[test]
fn sort_preserves_length (mut vec in prop::collection::vec (any::<i32 >(), 0 ..100 )) {
let original_len = vec.len ();
vec.sort ();
assert_eq! (vec.len (), original_len);
}
#[test]
fn sort_produces_ordered_output (mut vec in prop::collection::vec (any::<i32 >(), 0 ..100 )) {
vec.sort ();
for window in vec.windows (2 ) {
assert! (window[0 ] <= window[1 ]);
}
}
}
カスタムストラテジー use proptest::prelude::*;
fn valid_email () -> impl Strategy <Value = String > {
("[a-z]{1,10}" , "[a-z]{1,5}" )
.prop_map (|(user, domain)| format! ("{user}@{domain}.com" ))
}
proptest! {
#[test]
fn accepts_valid_emails (email in valid_email ()) {
assert! (User::new ("Test" , &email).is_ok ());
}
}
mockall を使用したモック
トレイトベースのモック use mockall::{automock, predicate::eq};
#[automock]
trait UserRepository {
fn find_by_id (&self , id: u64 ) -> Option <User>;
fn save (&self , user: &User) -> Result <(), StorageError>;
}
#[test]
fn service_returns_user_when_found () {
let mut mock = MockUserRepository::new ();
mock.expect_find_by_id ()
.with (eq (42 ))
.times (1 )
.returning (|_| Some (User { id: 42 , name: "Alice" .into () }));
let service = UserService::new (Box ::new (mock));
let user = service.get_user (42 ).unwrap ();
assert_eq! (user.name, "Alice" );
}
#[test]
fn service_returns_none_when_not_found () {
let mut mock = MockUserRepository::new ();
mock.expect_find_by_id ()
.returning (|_| None );
let service = UserService::new (Box ::new (mock));
assert! (service.get_user (99 ).is_none ());
}
ドキュメントテスト
実行可能なドキュメント
pub fn add (a: i32 , b: i32 ) -> i32 {
a + b
}
pub fn parse_config (input: &str ) -> Result <Config, ParseError> {
todo!()
}
Criterionを使用したベンチマーク
[dev-dependencies]
criterion = { version = "0.5" , features = ["html_reports" ] }
[[bench]]
name = "benchmark"
harness = false
use criterion::{black_box, criterion_group, criterion_main, Criterion};
fn fibonacci (n: u64 ) -> u64 {
match n {
0 | 1 => n,
_ => fibonacci (n - 1 ) + fibonacci (n - 2 ),
}
}
fn bench_fibonacci (c: &mut Criterion) {
c.bench_function ("fib 20" , |b| b.iter (|| fibonacci (black_box (20 ))));
}
criterion_group!(benches, bench_fibonacci);
criterion_main!(benches);
テストカバレッジ
カバレッジの実行
cargo llvm-cov
cargo llvm-cov --html
cargo llvm-cov --lcov > lcov.info
cargo llvm-cov --fail-under-lines 80
カバレッジ目標 コードの種類 目標 クリティカルなビジネスロジック 100% パブリックAPI 90%以上 汎用コード 80%以上 生成済み / FFIバインディング 除外
テストコマンド cargo test
cargo test -- --nocapture
cargo test test_name
cargo test --lib
cargo test --test api_test
cargo test --doc
cargo test --no-fail-fast
cargo test -- --ignored
ベストプラクティス
まずテストを書く (TDD)
単体テストには #[cfg(test)] モジュールを使用する
実装ではなく動作をテストする
シナリオを説明する記述的なテスト名を使用する
より良いエラーメッセージのために assert! より assert_eq! を優先する
クリーンなエラー出力のために Result を返すテストでは ? を使用する
テストを独立させる——共有の可変状態なし
Result::is_err() をテストできる場合に #[should_panic] を使用する
すべてをモックする——可能なら統合テストを優先する
フレーキーなテストを無視する——修正または分離する
テストで sleep() を使用する——チャンネル、バリア、または tokio::time::pause() を使用する
エラーパスのテストをスキップする
CI統合
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy, rustfmt
- name: Check formatting
run: cargo fmt --check
- name: Clippy
run: cargo clippy -- -D warnings
- name: Run tests
run: cargo test
- uses: taiki-e/install-action@cargo-llvm-cov
- name: Coverage
run: cargo llvm-cov --fail-under-lines 80
覚えておくこと :テストはドキュメントである。コードをどのように使うべきかを示している。明確に書き、最新の状態を保つこと。