Rust incluye un framework de testing integrado en cargo test. No necesitas librerías externas para los casos habituales: unit tests junto al código, integration tests en un directorio separado y doc tests en los comentarios de documentación. Para benchmarks de rendimiento, Criterion es el estándar de facto.
Unit tests: junto al código, acceso a funciones privadas
// src/lib.rs
fn sumar(a: i32, b: i32) -> i32 {
a + b
}
fn dividir(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
#[cfg(test)]
mod tests {
use super::*; // accede a funciones privadas del módulo padre
#[test]
fn test_sumar() {
assert_eq!(sumar(2, 3), 5);
assert_eq!(sumar(-1, 1), 0);
}
#[test]
fn test_dividir_normal() {
assert_eq!(dividir(10.0, 2.0), Some(5.0));
}
#[test]
fn test_dividir_por_cero() {
assert_eq!(dividir(5.0, 0.0), None);
}
#[test]
#[should_panic(expected = "división")]
fn test_panic() {
panic!("división por cero");
}
#[test]
#[ignore = "requiere conexión a base de datos"]
fn test_lento() {
// cargo test -- --include-ignored para ejecutarlo
}
}
Integration tests: en el directorio tests/
// tests/integracion.rs
// Solo puede usar la API pública de la librería
use mi_crate::{sumar, dividir};
#[test]
fn suma_positivos() {
assert_eq!(sumar(10, 20), 30);
}
#[test]
fn dividir_resultado_correcto() {
let resultado = dividir(9.0, 3.0).unwrap();
assert!((resultado - 3.0).abs() < f64::EPSILON);
}
// Módulo de utilidades compartido entre tests
// tests/common/mod.rs
pub fn datos_prueba() -> Vec<i32> {
vec![1, 2, 3, 4, 5]
}
// tests/otro_test.rs
mod common;
#[test]
fn usar_utilidades() {
let d = common::datos_prueba();
assert_eq!(d.len(), 5);
}
Doc tests: ejemplos ejecutables en la documentación
/// Suma dos números enteros.
///
/// # Ejemplos
///
/// ```
/// let resultado = mi_crate::sumar(2, 3);
/// assert_eq!(resultado, 5);
/// ```
///
/// ```
/// // También con negativos
/// assert_eq!(mi_crate::sumar(-1, 1), 0);
/// ```
pub fn sumar(a: i32, b: i32) -> i32 {
a + b
}
/// Muestra código que no compila (no se ejecuta como test)
///
/// ```compile_fail
/// let x: i32 = "esto no compila";
/// ```
///
/// Código que no se ejecuta pero se muestra:
/// ```no_run
/// mi_crate::conectar_bbdd(); // requiere servidor real
/// ```
pub fn placeholder() {}
Aserciones útiles
#[cfg(test)]
mod tests {
#[test]
fn aserciones() {
// assert_eq! y assert_ne!
assert_eq!(2 + 2, 4, "la suma falló con valor {}", 2 + 2);
assert_ne!("hola", "adios");
// assert! para condiciones booleanas
assert!(vec![1, 2, 3].contains(&2));
// Comparar floats con tolerancia
let pi = std::f64::consts::PI;
assert!((pi - 3.14159).abs() < 0.0001);
// Verificar Result y Option
let resultado: Result<i32, &str> = Ok(42);
assert!(resultado.is_ok());
assert_eq!(resultado.unwrap(), 42);
}
#[test]
fn test_retorna_result() -> Result<(), String> {
// Los tests pueden devolver Result; Err se muestra como fallo
"42".parse::<i32>().map_err(|e| e.to_string())?;
Ok(())
}
}
Benchmarks con Criterion
// Cargo.toml
// [dev-dependencies]
// criterion = { version = "0.5", features = ["html_reports"] }
//
// [[bench]]
// name = "mi_benchmark"
// harness = false
// benches/mi_benchmark.rs
use criterion::{criterion_group, criterion_main, Criterion, BenchmarkId};
fn fibonacci(n: u64) -> u64 {
match n {
0 | 1 => n,
_ => fibonacci(n - 1) + fibonacci(n - 2),
}
}
fn benchmark_fibonacci(c: &mut Criterion) {
let mut group = c.benchmark_group("fibonacci");
for i in [10u64, 15, 20].iter() {
group.bench_with_input(BenchmarkId::from_parameter(i), i, |b, &n| {
b.iter(|| fibonacci(n));
});
}
group.finish();
}
criterion_group!(benches, benchmark_fibonacci);
criterion_main!(benches);
// Ejecutar: cargo bench
// Genera informe HTML en target/criterion/
Comandos útiles de cargo test
// Ejecutar todos los tests
// cargo test
// Ejecutar solo tests que contengan "sumar" en el nombre
// cargo test sumar
// Mostrar output de println! (normalmente capturado)
// cargo test -- --nocapture
// Ejecutar tests ignorados
// cargo test -- --include-ignored
// Ejecutar un único test por nombre exacto
// cargo test tests::test_sumar -- --exact
// Tests de integración en un fichero concreto
// cargo test --test integracion
Resumen
- Los unit tests van en el mismo fichero con
#[cfg(test)]; tienen acceso a items privados. - Los integration tests van en
tests/y solo ven la API pública. - Los doc tests en comentarios
///se ejecutan concargo testy mantienen los ejemplos sincronizados con el código. #[should_panic],#[ignore]y devolverResultson las anotaciones más útiles.- Criterion provee benchmarks estadísticamente robustos con informes HTML.
