Haz tus docs ejecutables: doc tests
¿Y si la documentación incorrecta rompiera tu build? Los docs se pudren: el ejemplo de tu README se aleja del API real y nadie lo nota hasta que un usuario lo copia y abre un bug report confundido. Los doc tests invierten el incentivo: los ejemplos de tus comentarios y de tu README se compilan y se ejecutan como parte de la suite de tests, así que la documentación obsoleta rompe el CI en lugar de fallarle a tus usuarios. Tus docs no pueden mentir, porque tienen que ejecutarse.
Cómo funcionan los doc tests de Rust
Cada bloque de código dentro de un comentario de documentación /// es un test. cargo test los ejecuta junto con tus tests unitarios; cargo test --doc ejecuta solo los doc tests.
/// Adds two numbers.
///
/// ```
/// let sum = mycrate::add(2, 3);
/// assert_eq!(sum, 5);
/// ```
pub fn add(a: i32, b: i32) -> i32 { a + b }
Rustdoc preprocesa cada bloque para que los ejemplos cortos sigan siendo cortos: envuelve el código en fn main() si no lo escribiste e inyecta extern crate mycrate; por ti. Renombra add más tarde y este bloque deja de compilar — el build se pone en rojo antes de que los docs queden mal.
Las líneas que empiezan con # se compilan pero no se muestran, así el lector ve 2 líneas de señal mientras el compilador ve toda la preparación:
/// ```
/// # fn setup() -> mycrate::Client { mycrate::Client::new() }
/// let client = setup();
/// client.ping();
/// ```
No todos los ejemplos deben ejecutarse igual
Algunos ejemplos tocan la red, otros están rotos a propósito, otros deben fallar. Rustdoc le da a cada bloque un atributo en lugar de una excusa:
| Atributo | Qué hace | Cuándo usarlo |
|---|---|---|
| (ninguno) | Compila y ejecuta | El default — prefiérelo |
no_run | Compila, no ejecuta | Llamadas de red, efectos secundarios |
should_panic | Debe hacer panic para pasar | Documentar modos de falla |
compile_fail | Debe fallar al compilar | Mostrar lo que el sistema de tipos rechaza |
ignore | Se omite por completo | Último recurso — no prueba nada |
compile_fail es el subestimado: te permite documentar "este mal uso no compila" y que el CI verifique que ese mal uso sigue sin compilar.
Testea también el README
El README es el doc que se desactualiza más rápido, porque vive fuera de la vista del compilador. Tráelo adentro:
#[doc = include_str!("../README.md")]
#[cfg(doctest)]
pub struct ReadmeDoctests;
Cada bloque de código Rust de tu README ahora es un doc test. La condición #[cfg(doctest)] mantiene el struct fuera de tu API pública y de los docs renderizados.
La misma idea fuera de Rust
El principio no depende del lenguaje — los ejemplos ejecutables son décadas anteriores a Rust:
doctestde Python ejecuta las sesiones>>>de tus docstrings:python -m doctest -v example.py, odoctest.testfile("README.txt")para archivos de texto.- pytest los recolecta en toda la suite:
pytest --doctest-modulespara docstrings,--doctest-glob="*.md"para incluir tus docs en Markdown en la misma corrida. - ¿Tu stack no tiene runner de doc tests? Extrae los bloques de código de tus docs en CI y compílalos con un script. El mecanismo importa menos que la propiedad: los ejemplos que no se ejecutan son afirmaciones, no documentación.
Movidas de usuario avanzado
- Escribe el doc test primero. Para un API público es mejor motor de TDD que un test unitario: te obliga a diseñar la llamada que un desconocido va a escribir de verdad.
- Usa
?con limpieza. Oculta la plomería delResultpara que los ejemplos muestren manejo de errores sin boilerplate: termina el bloque con un# Ok::<(), io::Error>(())oculto. - Los doc tests son combustible para agentes de IA. Los agentes de código leen tus docs para aprender tu API — y heredan cada ejemplo obsoleto como semilla de alucinación. Un crate con doc tests le da a los agentes ejemplos demostrablemente vigentes, y a ti un build en rojo apenas un refactor del agente rompa un contrato documentado.
- Presupuesta el tiempo de ejecución. Históricamente cada doc test de Rust se compilaba como su propio crate; la edición 2024 fusiona los doc tests compatibles para reducir ese costo (desactívalo por bloque con
standalone_crate).
Recursos
- rustdoc: Documentation tests — atributos, líneas ocultas, reglas de preprocesamiento
- rustdoc: el patrón
include_str!para el README - Módulo
doctestde Python —testmod,testfile, option flags - pytest: How to run doctests —
--doctest-modules,--doctest-glob
¿Construyendo una funcionalidad con IA? Yeda AI diseña, audita y lleva a producción sistemas LLM.