Keyboard shortcuts

Press or to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Tests

ZeroClaw utilise une taxonomie de tests à cinq niveaux reposant sur l’organisation du système de fichiers. Chaque niveau a une frontière différente et un coût différent ; choisissez le niveau le plus bas qui prouve ce que vous devez prouver.

Lorsqu’une PR revendique un comportement qu’un utilisateur exécute, clique, envoie, installe ou observe directement, utilisez la preuve de limite utilisateur pour identifier le plus petit test ou la plus petite vérification manuelle qui atteint cette limite.

Les cinq niveaux

NiveauCe que cela testeLimiteOù il se trouve
UnitéUne seule fonction ou structTout est simuléLes blocs #[cfg(test)] dans src/** ou tests.rs co-localisés
ComposantUn sous-système à l’intérieur de sa propre limiteSous-système réel, tout le reste est simulétests/component/
IntégrationPlusieurs composants internes câblés ensembleVrais internes, API externes simuléestests/integration/
SystèmeRequête complète → réponse à travers toutes les limites internesSeules les API externes sont simuléestests/system/
En directPile complète avec de vrais services externesRien de simulé, #[ignore]tests/live/

Plus deux répertoires non liés aux tests :

RépertoireObjectif
tests/manual/Scripts de test pilotés manuellement (shell, Python), exécutés directement, et non via cargo
tests/support/Infrastructure de mocks partagée, pas un binaire de test, incluse via mod support; depuis chaque niveau

Exécution des tests

sh

cargo test                                  # unitaire + composant + intégration + système
cargo test --lib                            # unité uniquement
cargo test --test component                 # uniquement le composant
cargo test --test integration               # intégration uniquement
cargo test --test system                    # système uniquement
cargo test --test live -- --ignored         # live (nécessite des identifiants API)
cargo test --test integration agent         # filtre au sein d'un niveau
cargo nextest run --locked --workspace --exclude zeroclaw-desktop  # ce que la CI exécute
./scripts/ci/parallel_runtime_test_gate.sh  # tests répétés de l’environnement d’exécution et du canal dans un même processus
./dev/ci.sh all                             # batterie CI complète (Docker)
./dev/ci.sh firmware-protocol               # passerelle hôte autonome du protocole de micrologiciel (Docker)
./dev/ci.sh test-component                  # commandes CI spécifiques au niveau (Docker)

La commande firmware-protocol vérifie le crate autonome firmware/zeroclaw-fw-protocol, qui se trouve en dehors du workspace Cargo racine. scripts/ci/firmware_protocol_gate.sh est la définition canonique de ses vérifications de formatage, de Clippy strict et de tests verrouillés ; le CI requis et le hook pre-push invoquent le même assistant.

La passerelle d’exécution parallèle répète l’intégralité des binaires de test des bibliothèques zeroclaw-runtime et zeroclaw-channels avec 16 threads de harnais. L’exécution des binaires complets est intentionnelle : elle détecte les interférences entre les tests à mutation d’état et les tours d’agent sans lien apparent que les exécutions de tests filtrés ne peuvent pas exposer. La CI obligatoire exécute cette passerelle dans un job distinct pour les modifications apportées à l’un ou l’autre des crates, aux manifestes de dépendances de l’espace de travail ou aux propres fichiers CI de la passerelle. Les autres PR la sautent ; les poussées vers master et les exécutions de la file d’attente de fusion conservent le filet de sécurité de régression complet. Remplacez le nombre de répétitions avec ZEROCLAW_PARALLEL_TEST_RUNS et les threads de harnais avec ZEROCLAW_PARALLEL_TEST_THREADS.

Choisir un niveau pour un nouveau test

  1. Tester un sous-système de manière isolée ? → tests/component/
  2. Tester plusieurs composants câblés ensemble ? → tests/integration/
  3. Tester le flux complet des messages de bout en bout ? → tests/system/
  4. Requiert de vraies clés API ? → tests/live/ avec #[ignore]

Après avoir créé le fichier, ajoutez-le au mod.rs du niveau et utilisez l’infrastructure partagée de tests/support/.

Infrastructure partagée

Chaque binaire de test inclut mod support;, rendant les mocks partagés disponibles sous forme de crate::support::*.

ModuleContenu
mock_model_provider.rsMockModelProvider (script FIFO), RecordingModelProvider (capture les requêtes), TraceLlmModelProvider (relecture de fixtures JSON)
mock_tools.rsEchoTool, CountingTool, FailingTool, RecordingTool
mock_channel.rsTestChannel (capture les envois, enregistre les événements de frappe)
helpers.rsmake_memory(), make_observer(), build_agent(), text_response(), tool_response(), StaticRecallMemory
trace.rsTypes LlmTrace, TraceTurn, TraceStep + LlmTrace::from_file()
assertions.rsverify_expects() pour l’assertion de trace déclarative

Utilisation typique :

#![allow(unused)]
fn main() {
use crate::support::{MockModelProvider, EchoTool, CountingTool};
use crate::support::helpers::{build_agent, text_response, tool_response};
}

Fixtures de trace JSON

Les fixtures de trace sont des scripts de réponses LLM préenregistrés, stockés sous forme de fichiers JSON dans tests/fixtures/traces/. Ils remplacent la configuration de mocks en ligne par des scripts de conversation déclaratifs, bien plus faciles à lire et à modifier que des chaînes mockall.

Comment ça marche :

  1. TraceLlmModelProvider charge une fixture et implémente le trait ModelProvider.
  2. Chaque appel à provider.chat() renvoie l’étape suivante du jeu d’essais dans l’ordre FIFO.
  3. Les outils réels s’exécutent normalement (EchoTool traite effectivement ses arguments).
  4. Après tous les tours, verify_expects() vérifie les assertions déclaratives.
  5. Si l’appel de l’agent au fournisseur dépasse le nombre d’étapes, le test échoue.

Format du jeu de données :

{
  "model_name": "test-name",
  "tours": [
    {
      "entrée_utilisateur": "Message utilisateur",
      étapes: [
        {
          "réponse": {
            "type": « texte »,
            "contenu": "Réponse du LLM",
            "jetons_d_entrée": 20,
            "output_tokens": 10
          }
        }
      ]
    }
  ],
  "attend": {
    "response_contains": ["texte attendu"],
    "outils_utilisés": ["echo"],
    "max_tool_calls": 1
  }
}

Types de réponse : "text" (texte brut) ou "tool_calls" (requêtes LLM pour exécuter des outils).

Attend les champs : response_contains, response_not_contains, tools_used, tools_not_used, max_tool_calls, all_tools_succeeded, response_matches (regex).

Conventions de test en direct

Les tests en conditions réelles sollicitent de véritables services externes et engendrent des coûts réels ; ils sont marqués #[ignore] par défaut et ne s’exécutent qu’avec une activation explicite.

  • Toujours #[ignore]. Ne laissez jamais un test en direct s’exécuter lors d’un cargo test normal.
  • Lisez les identifiants depuis env::var("ZEROCLAW_TEST_*"). Ne lisez pas la configuration de l’opérateur ; les tests en conditions réelles doivent être hermétiques.
  • Exécutez avec cargo test --test live -- --ignored --nocapture.

Les tests de base de données sont des tests d’intégration

Ne mockez pas SQLite pour les tests qui exercent le schéma ou le SQL ; les tests d’intégration doivent utiliser une vraie base de données. La classe de bugs « le mock passe mais la prod échoue » est bien réelle et nous en avons déjà fait les frais.

Tests manuels

tests/manual/ contient des scripts de tests manuels destinés à être exécutés par un humain et qui ne peuvent pas être automatisés via cargo test. Exécutez-les directement. Les tests de fumée manuels spécifiques à une chaîne se trouvent sous tests/manual/<channel>/.