Communitygithub.com

rust-testing

Modèles de test Rust comprenant les tests unitaires, d'intégration, asynchrones, basés sur les propriétés, le mocking et la couverture. Suit la méthodologie TDD. À utiliser lors de l'écriture de tests Rust — unitaires, d'intégration, asynchrones, basés sur les propriétés ou de couverture.

Qu'est-ce que rust-testing ?

Cette compétence fournit un guide complet des modèles de test Rust, couvrant les tests unitaires, d'intégration, asynchrones, basés sur les propriétés, le mocking et la couverture de code, en suivant la méthodologie TDD (RED-GREEN-REFACTOR). Elle est idéale pour les développeurs utilisant des IDE/éditeurs prenant en charge Rust, comme VS Code avec rust-analyzer, ou des agents IA comme Codex et Cursor. La compétence inclut des exemples concrets pour chaque type de test : utilisation de `#[test]` et `#[cfg(test)]` pour les tests unitaires, `rstest` pour les tests paramétrés, `proptest` pour les tests basés sur les propriétés, `mockall` pour le mocking, et `cargo-llvm-cov` pour la couverture. Elle explique également l'organisation des tests d'intégration dans le dossier `tests/`, les tests de documentation, les benchmarks avec Criterion, et l'intégration CI avec GitHub Actions. Les meilleures pratiques recommandent d'écrire les tests en premier, d'utiliser des noms descriptifs, et de tester le comportement plutôt que l'implémentation.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/rust-testing

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Modèles de test Rust

Modèles de test Rust complets pour écrire des tests fiables et maintenables suivant la méthodologie TDD.

Quand l'utiliser

  • Écrire de nouvelles fonctions, méthodes ou traits Rust
  • Ajouter une couverture de test à du code existant
  • Créer des benchmarks pour du code critique en performance
  • Implémenter des tests basés sur les propriétés pour la validation d'entrée
  • Suivre le flux de travail TDD dans les projets Rust

Comment ça fonctionne

  1. Identifier le code cible — Trouver la fonction, le trait ou le module à tester
  2. Écrire un test — Utiliser #[test] dans un module #[cfg(test)], rstest pour les tests paramétrés, ou proptest pour les tests basés sur les propriétés
  3. Moquer les dépendances — Utiliser mockall pour isoler l'unité testée
  4. Exécuter les tests (ROUGE) — Vérifier que le test échoue avec l'erreur attendue
  5. Implémenter (VERT) — Écrire le code minimal pour réussir
  6. Refactoriser — Améliorer tout en gardant les tests verts
  7. Vérifier la couverture — Utiliser cargo-llvm-cov, viser 80%+

Flux de travail TDD pour Rust

Le cycle ROUGE-VERT-REFACTORISATION

ROUGE       → Écrire d'abord un test qui échoue
VERT        → Écrire le code minimal pour réussir le test
REFACTORISATION → Améliorer le code tout en gardant les tests verts
RÉPÉTER     → Continuer avec l'exigence suivante

TDD pas à pas en Rust

// ROUGE : Écrire le test d'abord, utiliser todo!() comme 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 → panique avec 'not yet implemented'
// VERT : Remplacer todo!() par l'implémentation minimale
pub fn add(a: i32, b: i32) -> i32 { a + b }
// cargo test → SUCCÈS, puis REFACTORISER tout en gardant les tests verts

Tests unitaires

Organisation des tests au niveau du module

// 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!("email invalide : {email}"));
        }
        Ok(Self { name: name.into(), email })
    }

    pub fn display_name(&self) -> &str {
        &self.name
    }
}

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

    #[test]
    fn crée_un_utilisateur_avec_un_email_valide() {
        let user = User::new("Alice", "[email protected]").unwrap();
        assert_eq!(user.display_name(), "Alice");
        assert_eq!(user.email, "[email protected]");
    }

    #[test]
    fn rejette_un_email_invalide() {
        let result = User::new("Bob", "pas-un-email");
        assert!(result.is_err());
        assert!(result.unwrap_err().contains("email invalide"));
    }
}

Macros d'assertion

assert_eq!(2 + 2, 4);                                    // Égalité
assert_ne!(2 + 2, 5);                                    // Inégalité
assert!(vec![1, 2, 3].contains(&2));                     // Booléen
assert_eq!(value, 42, "attendu 42 mais obtenu {value}"); // Message personnalisé
assert!((0.1_f64 + 0.2 - 0.3).abs() < f64::EPSILON);   // Comparaison flottante

Test des erreurs et des paniques

Test des retours Result

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

    // Vérifier une variante d'erreur spécifique
    let err = result.unwrap_err();
    assert!(matches!(err, ConfigError::ParseError(_)));
}

#[test]
fn parse_réussit_pour_une_entrée_valide() -> Result<(), Box<dyn std::error::Error>> {
    let config = parse_config(r#"{"port": 8080}"#)?;
    assert_eq!(config.port, 8080);
    Ok(()) // Le test échoue si n'importe quel ? renvoie Err
}

Test des paniques

#[test]
#[should_panic]
fn panique_sur_une_entrée_vide() {
    process(&[]);
}

#[test]
#[should_panic(expected = "index out of bounds")]
fn panique_avec_un_message_spécifique() {
    let v: Vec<i32> = vec![];
    let _ = v[0];
}

Tests d'intégration

Structure des fichiers

ma_bibliothèque/
├── src/
│   └── lib.rs
├── tests/              # Tests d'intégration
│   ├── api_test.rs     # Chaque fichier est un binaire de test séparé
│   ├── db_test.rs
│   └── common/         # Utilitaires de test partagés
│       └── mod.rs

Écrire des tests d'intégration

// tests/api_test.rs
use ma_bibliothèque::{App, Config};

#[test]
fn cycle_de_vie_complet_dune_requête() {
    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");
}

Tests asynchrones

Avec Tokio

#[tokio::test]
async fn récupère_les_données_avec_succès() {
    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 gère_le_timeout() {
    use std::time::Duration;
    let result = tokio::time::timeout(
        Duration::from_millis(100),
        opération_lente(),
    ).await;

    assert!(result.is_err(), "aurait dû expirer");
}

Modèles d'organisation des tests

Tests paramétrés avec rstest

use rstest::{rstest, fixture};

#[rstest]
#[case("hello", 5)]
#[case("", 0)]
#[case("rust", 4)]
fn test_longueur_chaîne(#[case] input: &str, #[case] expected: usize) {
    assert_eq!(input.len(), expected);
}

// Fixtures
#[fixture]
fn test_bd() -> TestBd {
    TestBd::new_in_memory()
}

#[rstest]
fn test_insertion(test_bd: TestBd) {
    test_bd.insert("clé", "valeur");
    assert_eq!(test_bd.get("clé"), Some("valeur".into()));
}

Aides de test

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

    /// Crée un utilisateur de test avec des valeurs par défaut sensibles.
    fn créer_utilisateur(nom: &str) -> Utilisateur {
        Utilisateur::new(nom, &format!("{nom}@test.com")).unwrap()
    }

    #[test]
    fn affichage_utilisateur() {
        let user = créer_utilisateur("alice");
        assert_eq!(user.display_name(), "alice");
    }
}

Tests basés sur les propriétés avec proptest

Tests de propriétés de base

use proptest::prelude::*;

proptest! {
    #[test]
    fn aller_retour_codage_décodage(input in ".*") {
        let encoded = coder(&input);
        let decoded = décoder(&encoded).unwrap();
        assert_eq!(input, decoded);
    }

    #[test]
    fn tri_conserve_la_longueur(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
        let longueur_originale = vec.len();
        vec.sort();
        assert_eq!(vec.len(), longueur_originale);
    }

    #[test]
    fn tri_produit_un_ensemble_ordonné(mut vec in prop::collection::vec(any::<i32>(), 0..100)) {
        vec.sort();
        for window in vec.windows(2) {
            assert!(window[0] <= window[1]);
        }
    }
}

Stratégies personnalisées

use proptest::prelude::*;

fn email_valide() -> impl Strategy<Value = String> {
    ("[a-z]{1,10}", "[a-z]{1,5}")
        .prop_map(|(user, domaine)| format!("{user}@{domaine}.com"))
}

proptest! {
    #[test]
    fn accepte_les_emails_valides(email in email_valide()) {
        assert!(Utilisateur::new("Test", &email).is_ok());
    }
}

Mocking avec mockall

Mocking basé sur les traits

use mockall::{automock, predicate::eq};

#[automock]
trait UtilisateurRepository {
    fn trouver_par_id(&self, id: u64) -> Option<Utilisateur>;
    fn sauvegarder(&self, user: &Utilisateur) -> Result<(), ErreurStockage>;
}

#[test]
fn service_retourne_utilisateur_quand_trouvé() {
    let mut mock = MockUtilisateurRepository::new();
    mock.expect_trouver_par_id()
        .with(eq(42))
        .times(1)
        .returning(|_| Some(Utilisateur { id: 42, name: "Alice".into() }));

    let service = UtilisateurService::new(Box::new(mock));
    let user = service.obtenir_utilisateur(42).unwrap();
    assert_eq!(user.name, "Alice");
}

#[test]
fn service_retourne_none_quand_non_trouvé() {
    let mut mock = MockUtilisateurRepository::new();
    mock.expect_trouver_par_id()
        .returning(|_| None);

    let service = UtilisateurService::new(Box::new(mock));
    assert!(service.obtenir_utilisateur(99).is_none());
}

Tests de documentation

Documentation exécutable

/// Ajoute deux nombres ensemble.
///
/// # Exemples
///
/// ```
/// use ma_bibliothèque::add;
///
/// assert_eq!(add(2, 3), 5);
/// assert_eq!(add(-1, 1), 0);
/// ```
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

/// Analyse une chaîne de configuration.
///
/// # Erreurs
///
/// Renvoie `Err` si l'entrée n'est pas un TOML valide.
///
/// ```no_run
/// use ma_bibliothèque::parse_config;
///
/// let config = parse_config(r#"port = 8080"#).unwrap();
/// assert_eq!(config.port, 8080);
/// ```
///
/// ```no_run
/// use ma_bibliothèque::parse_config;
///
/// assert!(parse_config("}{invalid").is_err());
/// ```
pub fn parse_config(input: &str) -> Result<Config, ErreurParse> {
    todo!()
}

Benchmarking avec 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);

Couverture de test

Exécution de la couverture

# Installation : cargo install cargo-llvm-cov (ou utiliser taiki-e/install-action dans CI)
cargo llvm-cov                    # Résumé
cargo llvm-cov --html             # Rapport HTML
cargo llvm-cov --lcov > lcov.info # Format LCOV pour CI
cargo llvm-cov --fail-under-lines 80  # Échouer si en dessous du seuil

Cibles de couverture

Type de codeCible
Logique métier critique100%
API publique90%+
Code général80%+
Code généré / liaisons FFIExclure

Commandes de test

cargo test                        # Exécuter tous les tests
cargo test -- --nocapture         # Afficher la sortie println
cargo test nom_du_test            # Exécuter les tests correspondant au motif
cargo test --lib                  # Tests unitaires uniquement
cargo test --test api_test        # Tests d'intégration uniquement
cargo test --doc                  # Tests de documentation uniquement
cargo test --no-fail-fast         # Ne pas s'arrêter à la première erreur
cargo test -- --ignored           # Exécuter les tests ignorés

Bonnes pratiques

À FAIRE :

  • Écrire les tests D'ABORD (TDD)
  • Utiliser les modules #[cfg(test)] pour les tests unitaires
  • Tester le comportement, pas l'implémentation
  • Utiliser des noms de tests descriptifs qui expliquent le scénario
  • Préférer assert_eq! à assert! pour de meilleurs messages d'erreur
  • Utiliser ? dans les tests qui retournent Result pour une sortie d'erreur plus propre
  • Garder les tests indépendants — pas d'état mutable partagé

À NE PAS FAIRE :

  • Utiliser #[should_panic] quand vous pouvez tester Result::is_err() à la place
  • Moquer tout — préférer les tests d'intégration quand c'est faisable
  • Ignorer les tests instables — les corriger ou les mettre en quarantaine
  • Utiliser sleep() dans les tests — utiliser des canaux, des barrières ou tokio::time::pause()
  • Sauter les tests de chemins d'erreur

Intégration CI

# GitHub Actions
test:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - uses: dtolnay/rust-toolchain@stable
      with:
        components: clippy, rustfmt

    - name: Vérifier le formatage
      run: cargo fmt --check

    - name: Clippy
      run: cargo clippy -- -D warnings

    - name: Exécuter les tests
      run: cargo test

    - uses: taiki-e/install-action@cargo-llvm-cov

    - name: Couverture
      run: cargo llvm-cov --fail-under-lines 80

Souvenez-vous : Les tests sont une documentation. Ils montrent comment votre code est censé être utilisé. Écrivez-les clairement et maintenez-les à jour.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured 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.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Skills associés