9.1 Prueba
Esta traducción fue asistida por IA y revisada superficialmente. Si encuentras errores o algo no suena natural, escríbeme — lo agradezco.
Un artesano que comprobó la pieza hace un mes sabe una cosa: hace un mes funcionaba. Es un conocimiento útil - y en absoluto lo que se pregunta. “¿Funciona ahora?” es una pregunta sobre el presente. “Funcionaba cuando lo miré” es un recuerdo con forma de respuesta. La mayoría de la gente no nota la diferencia. Ese es el problema.
Lo que necesitas
Cada crate ya tiene tests - módulos #[cfg(test)] y mod tests junto al código. Comprueban
detalles desde dentro: ven los campos privados, llaman a funciones no públicas y viven en el
mismo alcance que el código en sí. Son tests unitarios - útiles, rápidos, pero limitados. No
responden a una pregunta distinta: ¿se comporta tq-core correctamente desde la perspectiva
del código que lo usa?
Un test de integración mira el crate desde fuera - exactamente como lo hace cualquier código que depende de él. Importa solo elementos públicos, no puede ver los internos, y comprueba lo que ve un usuario de la biblioteca. Rust no necesita herramientas especiales para esto - solo un lugar separado que compila de forma independiente.
En el workspace de tq, ese lugar será un nuevo crate tests/ que depende de tq-core.
Su único trabajo es contener tests.
La construcción
Añade "tests" a la lista de miembros del workspace. El Cargo.toml raíz:
[workspace]
members = ["crates/core", "crates/cli", "crates/api", "tests"]
resolver = "2"
Crea tests/Cargo.toml. Este crate no necesita src/lib.rs - solo targets de test y
dependencias:
[package]
name = "tq-tests"
version = "0.1.0"
edition = "2024"
[[test]]
name = "core"
path = "tests/core.rs"
[dev-dependencies]
tq-core = { path = "../crates/core" }
tempfile = "3"
tempfile es un crate para directorios temporales que se eliminan automáticamente cuando
termina el test. Cargo lo añade igual que cualquier otra dependencia.
Ahora tests/tests/core.rs. Cada test crea su propio directorio temporal - no se interfieren
entre sí y no dejan rastros en el disco:
use std::path::PathBuf;
use tq_core::persistence;
use tq_core::store::{Store, TaskStore};
#[test]
fn roundtrip_save_and_load() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tasks.json");
let mut store = TaskStore::new(vec![]);
store.add("Buy coffee").unwrap();
store.add("Write tests").unwrap();
persistence::save(store.all(), &path).unwrap();
let loaded = persistence::load(&path).unwrap();
assert_eq!(loaded.len(), 2);
assert_eq!(loaded[0].title, "Buy coffee");
assert_eq!(loaded[1].title, "Write tests");
}
#[test]
fn load_nonexistent_path_returns_empty() {
let path = PathBuf::from("/tmp/tq_test_nonexistent_proof.json");
let tasks = persistence::load(&path).unwrap();
assert!(tasks.is_empty());
}
#[test]
fn completed_task_survives_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("tasks.json");
let mut store = TaskStore::new(vec![]);
let id = store.add("Buy coffee").unwrap();
store.get_mut(id).unwrap().complete();
persistence::save(store.all(), &path).unwrap();
let loaded = persistence::load(&path).unwrap();
assert_eq!(loaded.len(), 1);
assert!(loaded[0].is_done());
}
Fíjate: use tq_core::persistence aquí, no use crate::persistence. El test no sabe nada
de la estructura interna del crate - ve solo lo que el crate exporta.
El resultado
$ make ci
...
Running tests/core.rs (target/debug/deps/core-...)
running 3 tests
test completed_task_survives_roundtrip ... ok
test load_nonexistent_path_returns_empty ... ok
test roundtrip_save_and_load ... ok
test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
La puerta incluye ahora el crate tq-tests. Cualquier cambio en persistence o TaskStore
que rompa el comportamiento externo no pasará por ella.
El código completo de
tqpara este capítulo está en9-shippable/01-proof/.
Lore: Lo que ve un test de integración
Un test unitario se declara dentro del crate - en un módulo #[cfg(test)] junto al código.
El compilador ve todo: campos privados, funciones sin pub, detalles ocultos al mundo
exterior. Es conveniente para comprobar la lógica interna, pero significa que un test puede
pasar por razones invisibles para un usuario de la biblioteca.
Un test de integración se compila como un crate separado. Importa tq-core como dependencia
- exactamente igual que
tq-cliotq-api. Ve solopub: tipos públicos, funciones públicas, traits públicos. Si el test no puede llamar a una función que necesita - esa función no es accesible desde fuera. Esto no es un problema a resolver con rodeos; es información sobre la interfaz.
La diferencia se nota más durante la refactorización: la estructura interna de TaskStore
puede cambiarse libremente - los tests unitarios necesitarán actualizarse, los de integración
no, siempre que el comportamiento externo siga siendo el mismo.