Introducción: Por qué las pruebas en Rust son simples y confiables
Rust fue diseñado desde el principio como un lenguaje que ayuda a escribir código seguro y confiable. Sin embargo, incluso el sistema de tipos más estricto no garantiza la corrección de la lógica de negocio. Aquí es donde las pruebas vienen al rescate. En Rust, el proceso de escribir pruebas está integrado directamente en la herramienta cargo, lo que lo hace increíblemente conveniente y accesible.
En este artículo, analizaremos tres niveles de pruebas: unit (pruebas unitarias), integration (pruebas de integración) y e2e (end-to-end). Aprenderá cómo escribir pruebas, cuándo usar cada enfoque y cómo automatizar la verificación de su código. El material será útil tanto para principiantes como para desarrolladores experimentados que están migrando a Rust.
Pruebas unitarias: Probando desde adentro
Las pruebas unitarias son el tipo de prueba más básico y común. Verifican funciones, métodos o módulos individuales de forma aislada del resto del sistema. En Rust, es común escribir pruebas unitarias directamente en el mismo archivo que el código bajo prueba, o en un archivo separado tests.rs dentro del módulo.
Cómo escribir pruebas unitarias en Rust
Para crear una prueba se utiliza el atributo #[test]. Las pruebas se colocan en un módulo con el atributo #[cfg(test)], que garantiza que el código de prueba no se incluya en la compilación final.
// src/lib.rs
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
pub fn divide(a: f64, b: f64) -> Result<f64, String> {
if b == 0.0 {
Err(String::from("Division by zero"))
} else {
Ok(a / b)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[test]
fn test_add_negative() {
assert_eq!(add(-1, 1), 0);
}
#[test]
fn test_divide_success() {
let result = divide(10.0, 2.0);
assert!(result.is_ok());
assert_eq!(result.unwrap(), 5.0);
}
#[test]
fn test_divide_by_zero() {
let result = divide(10.0, 0.0);
assert!(result.is_err());
assert_eq!(result.unwrap_err(), "Division by zero");
}
}
Ejecución de pruebas unitarias
Puede ejecutar las pruebas con un solo comando:
cargo test
La salida mostrará cuántas pruebas pasaron y cuántas fallaron. Para ejecutar una prueba específica, use un filtro:
cargo test test_add
Las pruebas unitarias son ideales para verificar casos límite, manejo de errores y corrección de algoritmos. Son rápidas y no requieren dependencias externas.
Pruebas de integración: Verificando la interacción de módulos
Las pruebas de integración verifican cómo diferentes módulos de su biblioteca o aplicación funcionan juntos. A diferencia de las pruebas unitarias, no tienen acceso a funciones internas (a menos que estén exportadas como pub). Las pruebas de integración se escriben en una carpeta separada tests/ a nivel de la raíz del proyecto.
Estructura de las pruebas de integración
Cree una carpeta tests/ en la raíz de su proyecto y agregue archivos con pruebas. Cada archivo se compilará como un crate independiente.
// tests/integration_test.rs
use my_crate; // nombre de su crate
#[test]
fn test_add_integration() {
assert_eq!(my_crate::add(10, 20), 30);
}
#[test]
fn test_divide_integration() {
let result = my_crate::divide(100.0, 4.0);
assert_eq!(result.unwrap(), 25.0);
}
Módulo común para pruebas
Si varias pruebas de integración utilizan las mismas funciones auxiliares, extráigalas a un módulo común. Para ello, cree la carpeta tests/common/mod.rs:
// tests/common/mod.rs
pub fn setup() {
// inicialización de logger, base de datos, etc.
println!("Setting up test environment...");
}
// tests/another_test.rs
mod common;
#[test]
fn test
```