Pruebas en Rust: desde pruebas unitarias hasta property-based testing
Rust es uno de los pocos lenguajes donde el sistema de tipos y propiedad ya en la etapa de compilación excluye clases enteras de errores (condiciones de carrera, punteros colgantes, doble liberación de memoria). Sin embargo, los errores lógicos, el manejo incorrecto de casos límite y la lógica de negocio incorrecta permanecen. Para detectarlos, el ecosistema de Rust ha desarrollado un potente conjunto de herramientas de prueba, desde el ejecutor de pruebas integrado hasta marcos avanzados para la generación de datos de prueba.
Herramientas integradas: cargo test
La herramienta de prueba básica está integrada directamente en el compilador y el gestor de paquetes. Para crear pruebas, basta con añadir un módulo con el atributo #[cfg(test)] y marcar las funciones con el atributo #[test].
// src/lib.rspub fn add(left: u64, right: u64) -> u64 { left + right}
#[cfg(test)]mod tests { use super::*;
#[test] fn test_add() { let result = add(2, 2); assert_eq!(result, 4); }
#[test] fn test_add_negative() { // u64 no admite números negativos — esto es un error de compilación // Pero si la función aceptara i64: // assert_eq!(add(-2, 2), 0); }}Ejecución: cargo test. La bandera -- --nocapture muestra println! de las pruebas. La bandera -- --test-threads=1 desactiva la ejecución paralela.
Pruebas de integración
Las pruebas de integración se encuentran en la carpeta tests/ a nivel de la raíz del proyecto. Cada archivo es un crate independiente que puede probar la API pública de la biblioteca.
// tests/integration_test.rsuse my_library::add;
#[test]fn test_integration() { assert_eq!(add(10, 20), 30);}Para probar crates binarios (con main.rs), se puede usar assert_cmd — ejecuta el binario como un proceso externo y verifica stdout/stderr.
Property-based testing: proptest y quickcheck
En lugar de escribir docenas de casos de prueba manualmente, el property-based testing genera miles de datos de entrada aleatorios y verifica invariantes. Los dos marcos principales son:
- proptest — moderno, flexible, con soporte para estrategias, filtrado y reducción de ejemplos (shrink).
- quickcheck — más antiguo, inspirado en Haskell QuickCheck, más simple pero menos potente.
Ejemplo con proptest:
use proptest::prelude::*;
fn reverse_string(s: &str) -> String { s.chars().rev().collect()}
proptest! { #[test] fn test_reverse_twice(s in ".*") { let reversed = reverse_string(&s); let double_reversed = reverse_string(&reversed); assert_eq!(s, double_reversed); }
#[test] fn test_reverse_length(s in ".*") { let reversed = reverse_string(&s); assert_eq!(s.len(), reversed.len()); }}Si una prueba falla, proptest encuentra el contraejemplo mínimo (shrinking).
Pruebas de código asíncrono: tokio-test
Para funciones asíncronas que trabajan con tokio, las pruebas normales no son adecuadas — se necesita un runtime asíncrono. tokio-test proporciona la macro #[tokio::test].
use tokio::time::{sleep, Duration};
async fn delayed_add(a: u32, b: u32) -> u32 { sleep(Duration::from_millis(10)).await; a + b}
#[tokio::test]async fn test_delayed_add() { let result = delayed_add(3, 5).await; assert_eq!(result, 8);}También se puede usar futures-test para trabajar con futures y async-std.
Mocks y stubs: mockall y mockito
Para aislar las pruebas de dependencias externas (BD, API, sistema de archivos
```También en la biblioteca
Envío de SMS en PHP: Revisión de bibliotecas API para integración
Caché en C++: Redis, capa de datos y otras herramientas
Geolocalización y mapas en Kotlin: descripción general de bibliotecas y herramientas
Monitoreo de aplicaciones TypeScript: herramientas y prácticas