Mmcp.market

rust-testing skill

by affaan-m·affaan-m/ECC·269k stars·MIT

Patrones de pruebas en Rust incluyendo pruebas unitarias, de integración, async, basadas en propiedades, mocking y cobertura. Sigue la metodología TDD.

A100/100content scan

Is the rust-testing skill safe?

Clean: nothing in its files matched our rules. We read 1 file in the folder on 2026-09-28.

No findings.

Install the rust-testing skill

A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.

git clone --depth 1 https://github.com/affaan-m/ECC.git /tmp/ECC
mkdir -p ~/.claude/skills
cp -r /tmp/ECC/docs/es/skills/rust-testing ~/.claude/skills/rust-testing
available in every project

In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub

The instructions your agent would load

SKILL.md as published, without the frontmatter. Read it on GitHub

Patrones de Pruebas Rust

Patrones completos de pruebas en Rust para escribir pruebas confiables y mantenibles siguiendo la metodología TDD.

Cuándo Usar

  • Escribir nuevas funciones, métodos o traits en Rust
  • Agregar cobertura de pruebas a código existente
  • Crear benchmarks para código con requisitos de rendimiento
  • Implementar pruebas basadas en propiedades para validación de entrada
  • Seguir el flujo de trabajo TDD en proyectos Rust

Cómo Funciona

  1. Identificar el código objetivo — Encontrar la función, trait o módulo a probar
  2. Escribir una prueba — Usar #[test] en un módulo #[cfg(test)], rstest para pruebas parametrizadas, o proptest para pruebas basadas en propiedades
  3. Mockear dependencias — Usar mockall para aislar la unidad bajo prueba
  4. Ejecutar pruebas (ROJO) — Verificar que la prueba falla con el error esperado
  5. Implementar (VERDE) — Escribir el código mínimo para que pase
  6. Refactorizar — Mejorar mientras se mantienen las pruebas en verde
  7. Verificar cobertura — Usar cargo-llvm-cov, objetivo 80%+

Flujo de Trabajo TDD en Rust

El Ciclo ROJO-VERDE-REFACTORIZAR

ROJO        → Escribir primero una prueba que falle
VERDE       → Escribir el código mínimo para que pase
REFACTORIZAR → Mejorar el código manteniendo las pruebas en verde
REPETIR     → Continuar con el siguiente requisito

TDD Paso a Paso en Rust

// ROJO: Escribir la prueba primero, usar todo!() como placeholder
pub fn add(a: i32, b: i32) -> i32 { todo!() }

#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_add() { assert_eq!(add(2, 3), 5); }
}
// cargo test → panic en 'not yet implemented'
// VERDE: Reemplazar todo!() con implementación mínima
pub fn add(a: i32, b: i32) -> i32 { a + b }
// cargo test → PASS, luego REFACTORIZAR manteniendo pruebas en verde

Pruebas Unitarias

Organización de Pruebas a Nivel de Módulo

// src/user.rs
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"));
    }
}

Macros de Aserción

assert_eq!(2 + 2, 4);                                    // Igualdad
assert_ne!(2 + 2, 5);                                    // Desigualdad
assert!(vec![1, 2, 3].contains(&2));                     // Booleano
assert_eq!(value, 42, "expected 42 but got {value}");    // Mensaje personalizado
assert!((0.1_f64 + 0.2 - 0.3).abs() < f64::EPSILON);   // Comparación de flotantes

Pruebas de Errores y Panics

Probar Retornos de Result

#[test]
fn parse_returns_error_for_invalid_input() {
    let result = parse_config("}{invalid");
    assert!(result.is_err());

    // Verificar variante de error específica
    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(()) // La prueba falla si algún ? retorna Err
}

Probar Panics

#[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];
}

Pruebas de Integración

Estructura de Archivos

my_crate/
├── src/
│   └── lib.rs
├── tests/              # Pruebas de integración
│   ├── api_test.rs     # Cada archivo es un binario de prueba separado
│   ├── db_test.rs
│   └── common/         # Utilidades de prueba compartidas
│       └── mod.rs

Escribir Pruebas de Integración

// tests/api_test.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");
}

Pruebas Async

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

Patrones de Organización de Pruebas

Pruebas Parametrizadas con 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);
}

// Fixtures
#[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()));
}

Helpers de Prueba

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

    /// Crea un usuario de prueba con valores predeterminados sensatos.
    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");
    }
}

Pruebas Basadas en Propiedades con proptest

Pruebas de Propiedades Básicas

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

Estrategias Personalizadas

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

Mocking con mockall

Mocking Basado en Traits

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

Pruebas de Documentación

Documentación Ejecutable

/// Suma dos números.
///
/// # Examples
///
/// ```
/// use my_crate::add;
///
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

/// Parsea una cadena de configuración.
///
/// # Errors
///
/// Retorna `Err` si la entrada no es TOML válido.
///
/// ```no_run
/// use my_crate::parse_config;
///
/// let config = parse_config(r#"port = 8080"#).unwrap();
/// assert_eq!(config.port, 8080);
/// ```
///
/// ```no_run
/// use my_crate::parse_config;
///
/// assert!(parse_config("}{invalid").is_err());
/// ```
pub fn parse_config(input: &str) -> Result<Config, ParseError> {
    todo!()
}

Benchmarks con Criterion

# Cargo.toml
[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }

[[bench]]
name = "benchmark"
harness = false
// benches/benchmark.rs
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);

Cobertura de Pruebas

Ejecutar Cobertura

# Instalar: cargo install cargo-llvm-cov (o usar taiki-e/install-action en CI)
cargo llvm-cov                    # Resumen
cargo llvm-cov --html             # Reporte HTML
cargo llvm-cov --lcov > lcov.info # Formato LCOV para CI
cargo llvm-cov --fail-under-lines 80  # Fallar si está por debajo del umbral

Objetivos de Cobertura

Comandos de Prueba

cargo test                        # Ejecutar todas las pruebas
cargo test -- --nocapture         # Mostrar salida de println
cargo test test_name              # Ejecutar pruebas que coincidan con el patrón
cargo test --lib                  # Solo pruebas unitarias
cargo test --test api_test        # Solo pruebas de integración
cargo test --doc                  # Solo pruebas de documentación
cargo test --no-fail-fast         # No detener al primer fallo
cargo test -- --ignored           # Ejecutar pruebas ignoradas

Buenas Prácticas

HACER:

  • Escribir pruebas PRIMERO (TDD)
  • Usar módulos #[cfg(test)] para pruebas unitarias
  • Probar comportamiento, no implementación
  • Usar nombres de prueba descriptivos que expliquen el escenario
  • Preferir assert_eq! sobre assert! para mejores mensajes de error
  • Usar ? en pruebas que retornan Result para salida de errores más limpia
  • Mantener las pruebas independientes — sin estado mutable compartido

NO HACER:

  • Usar #[shouldpanic] cuando se puede probar Result::iserr()
  • Mockear todo — preferir pruebas de integración cuando sea factible
  • Ignorar pruebas inestables — corregirlas o ponerlas en cuarentena
  • Usar sleep() en pruebas — usar canales, barreras o tokio::time::pause()
  • Omitir las pruebas de rutas de error

More skills from affaan-m/ECC

  • AaccessibilityWCAG 2.2 レベル AA 標準を用いてインクルーシブなデジタルプロダクトを設計・実装・監査します。Web 用のセマンティック ARIA および Web・ネイティブプラットフォーム(iOS/Android)のアクセシビリティトレイトを生成するために使用します。
  • Aagent-architecture-auditエージェントおよび LLM アプリケーション向けのフルスタック診断。12 層のエージェントスタックにおけるラッパーリグレッション、メモリ汚染、ツール規律の失敗、隠れた修復ループ、レンダリング破損を監査します。重要度順の発見事項とコードファーストの修正を生成します。エージェントアプリケーション、自律ループ、または LLM を活用した機能を構築する開発者に必須です。
  • Aagent-evalカスタムタスクでコーディングエージェント(Claude Code、Aider、Codex など)をヘッドツーヘッドで比較し、合格率、コスト、時間、一貫性のメトリクスを測定します
  • Aagent-harness-constructionAI エージェントのアクション空間、ツール定義、観測フォーマットを設計・最適化して完了率を向上させます。
  • Aagent-introspection-debuggingStructured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.
  • Aagent-introspection-debuggingキャプチャ、診断、封じ込め回復、内省レポートを使用した AI エージェント障害のための構造化された自己デバッグワークフロー。
  • Aagent-payment-x402タスクごとのバジェット、支出コントロール、ノンカストディアルウォレットを備えた x402 決済実行を AI エージェントに追加します。agentwallet-sdk を通じて Base をサポートし、OKX Payments / OKX エージェント決済プロトコルを通じて X Layer をサポートします。
  • Aagent-sortBuild an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.
  • Aagent-sort並行リポジトリ対応のレビューパスを使用して、スキル、コマンド、ルール、フック、エクストラを DAILY と LIBRARY のバケットに分類することで、特定のリポジトリ向けのエビデンスに基づいた ECC インストール計画を構築します。プロジェクトが完全なバンドルをロードする代わりに実際に必要なものに ECC をトリミングする必要がある場合に使用します。
  • Aagentic-engineeringOperate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when AI agents perform most implementation work and humans enforce quality and risk controls.
  • Aagentic-engineering評価ファースト実行、分解、コスト対応モデルルーティングを使用してエージェニックエンジニアとして動作します。
  • Aagentic-osClaude Code 上に永続的なマルチエージェントオペレーティングシステムを構築します。カーネルアーキテクチャ、スペシャリストエージェント、スラッシュコマンド、ファイルベースのメモリ、スケジュールされた自動化、外部データベースなしの状態管理をカバーします。

All agent skills → · MCP servers