Pruebas
ZeroClaw utiliza una taxonomía de pruebas de cinco niveles respaldada por la estructura del sistema de archivos. Cada nivel tiene un límite diferente y un costo diferente; elige el nivel más bajo que demuestre lo que necesitas demostrar.
Cuando un PR reclama un comportamiento que un usuario ejecuta, hace clic, envía, instala u observa directamente, usa User-boundary proof para identificar la prueba o comprobación manual más pequeña que alcance ese límite.
Los cinco niveles
| Nivel | Qué prueba | Límite | Dónde se encuentra |
|---|---|---|---|
| Unidad | Una sola función o estructura | Todo simulado | Bloques #[cfg(test)] en src/** o tests.rs en el mismo directorio |
| Componente | Un subsistema dentro de su propio límite | Subsystem real, todo lo demás simulado | tests/component/ |
| Integración | Múltiples componentes internos conectados entre sí | Interna real, APIs externas simuladas | tests/integration/ |
| Sistema | Solicitud completa → respuesta a través de todos los límites internos | Solo APIs externas simuladas | tests/system/ |
| En vivo | Pila completa con servicios externos reales | Nada simulado, #[ignore] | tests/live/ |
Más dos directorios que no son de pruebas:
| Directorio | Propósito |
|---|---|
tests/manual/ | Scripts de prueba ejecutados por humanos (shell, Python), que se ejecutan directamente, no a través de cargo |
tests/support/ | Infraestructura compartida de mocks, no un binario de pruebas, incluida como mod support; desde cada nivel |
Ejecutando pruebas
sh
cargo test # unidad + componente + integración + sistema
cargo test --lib # solo unidad
cargo test --test component # solo componente
cargo test --test integration # solo integración
cargo test --test system # solo del sistema
cargo test --test live -- --ignored # live (requiere credenciales de API)
cargo test --test integration agent # filtro dentro de un nivel
cargo nextest run --locked --workspace --exclude zeroclaw-desktop # qué ejecuta el CI
./scripts/ci/parallel_runtime_test_gate.sh # pruebas repetidas de tiempo de ejecución/canal en el mismo proceso
./dev/ci.sh all # batería completa de CI (Docker)
./dev/ci.sh firmware-protocol # puerta de enlace de host autónoma para el protocolo de firmware (Docker)
./dev/ci.sh test-component # comandos de CI específicos por nivel (Docker)
El comando firmware-protocol verifica el crate independiente firmware/zeroclaw-fw-protocol, que se encuentra fuera del espacio de trabajo raíz de Cargo. scripts/ci/firmware_protocol_gate.sh es la definición canónica de sus verificaciones de formato, Clippy estricto y pruebas con bloqueo; el CI requerido y el hook pre-push invocan el mismo helper.
La puerta de ejecución paralela repite los binarios de prueba completos de las bibliotecas zeroclaw-runtime y zeroclaw-channels con 16 hilos de arnés. Ejecutar los binarios completos es intencional: detecta interferencias entre pruebas que mutan estado y turnos de agente que de otro modo no están relacionados, las cuales las ejecuciones de pruebas filtradas no pueden exponer. Las ejecuciones de CI obligatorias ejecutan esta puerta en un trabajo separado para cambios en cualquiera de los dos crates, los manifiestos de dependencias del espacio de trabajo o los propios archivos de CI de la puerta. Los demás PRs la omiten; las inserciones en master y las ejecuciones de la cola de fusión conservan el respaldo completo de regresión. Reemplaza las repeticiones con ZEROCLAW_PARALLEL_TEST_RUNS y los hilos de arnés con ZEROCLAW_PARALLEL_TEST_THREADS.
Elegir un nivel para una nueva prueba
- ¿Probando un subsistema de forma aislada? →
tests/component/ - ¿Probando múltiples componentes conectados entre sí? →
tests/integration/ - ¿Probando el flujo completo del mensaje de extremo a extremo? →
tests/system/ - Requiere claves de API reales? →
tests/live/con#[ignore]
Después de crear el archivo, agrégalo al mod.rs del nivel y utiliza la infraestructura compartida de tests/support/.
Infraestructura compartida
Cada binario de prueba incluye mod support;, lo que hace que los mocks compartidos estén disponibles como crate::support::*.
| Módulo | Contenido |
|---|---|
mock_model_provider.rs | MockModelProvider (con guion FIFO), RecordingModelProvider (captura solicitudes), TraceLlmModelProvider (reproducción de fixtures JSON) |
mock_tools.rs | EchoTool, CountingTool, FailingTool, RecordingTool |
mock_channel.rs | TestChannel (captura los envíos, registra los eventos de escritura) |
helpers.rs | make_memory(), make_observer(), build_agent(), text_response(), tool_response(), StaticRecallMemory |
trace.rs | Tipos LlmTrace, TraceTurn, TraceStep + LlmTrace::from_file() |
assertions.rs | verify_expects() para la aserción de trazas declarativas |
Uso típico:
#![allow(unused)]
fn main() {
use crate::support::{MockModelProvider, EchoTool, CountingTool};
use crate::support::helpers::{build_agent, text_response, tool_response};
}
Fixtures de trazas JSON
Los fixtures de trazas son scripts predefinidos de respuestas de LLM almacenados como archivos JSON en tests/fixtures/traces/. Reemplazan la configuración de mocks en línea con scripts de conversación declarativos, mucho más fáciles de leer y editar que las cadenas de mockall.
Cómo funciona:
TraceLlmModelProvidercarga un fixture e implementa el traitModelProvider.- Cada llamada a
provider.chat()devuelve el siguiente paso del fixture en orden FIFO. - Las herramientas reales se ejecutan normalmente (
EchoToolprocesa realmente sus argumentos). - Después de todas las rondas,
verify_expects()verifica las aserciones declarativas. - Si el agente llama al proveedor más veces de las que hay pasos, la prueba falla.
Formato de fixture:
{
"nombre_modelo": "nombre-de-prueba",
"giros": [
{
"entrada de usuario": "Mensaje del usuario",
"pasos": [
{
"respuesta": {
"tipo": "texto",
"contenido": "Respuesta del LLM",
"tokens_de_entrada": 20,
"tokens_de_salida": 10
}
}
]
}
],
"espera": {
"response_contains": ["texto esperado"],
"herramientas_usadas": ["echo"],
"max_tool_calls": 1
}
}
Tipos de respuesta: "text" (texto plano) o "tool_calls" (solicitudes de herramientas LLM).
Espera los campos: response_contains, response_not_contains, tools_used, tools_not_used, max_tool_calls, all_tools_succeeded, response_matches (regex).
Convenciones de pruebas en vivo
Las pruebas en vivo acceden a servicios externos reales y cuestan dinero real; están marcadas con #[ignore] por defecto y solo se ejecutan con aceptación explícita.
- Siempre
#[ignore]. Nunca permitas que una prueba en vivo se ejecute durante uncargo testnormal. - Lee las credenciales desde
env::var("ZEROCLAW_TEST_*"). No leas la configuración del operador; las pruebas en vivo deben ser herméticas. - Ejecuta con
cargo test --test live -- --ignored --nocapture.
Las pruebas de base de datos son pruebas de integración
No simules SQLite con mocks en tests que ejercitan el esquema o SQL; los tests de integración deben usar una base de datos real. La clase de bugs “el mock pasa pero producción falla” es real y ya la hemos sufrido antes.
Pruebas manuales
tests/manual/ contiene scripts para pruebas realizadas por humanos que no pueden automatizarse mediante cargo test. Ejecútalos directamente. Las pruebas de humo manuales específicas del canal se encuentran bajo tests/manual/<channel>/.