24 - Escribiendo tests - mejorando la calidad del código

24 - Escribiendo tests - mejorando la calidad del código

¡Bienvenidos de nuevo a Rustaceo.es! Hoy nos enfocaremos en una parte fundamental para garantizar la calidad y fiabilidad de nuestros programas: las pruebas (tests) en Rust. Aprenderemos a escribir pruebas unitarias y de integración, y a usar herramientas para asegurar que nuestro código es robusto y libre de errores.

📁 Introducción a las pruebas en Rust #

Rust tiene soporte nativo para pruebas mediante su herramienta de construcción cargo. Al ejecutar cargo test, Rust buscará y ejecutará todas las pruebas definidas en el proyecto.

⚒ Creando pruebas unitarias #

Las pruebas unitarias se centran en comprobar el correcto funcionamiento de funciones y módulos individuales.

Ejemplo: prueba unitaria de una función de daño #

// src/lib.rs
pub fn calcular_dano(ataque: u16, defensa: u16, poder: u16) -> u16 {
    ((ataque as f32 / defensa as f32) * poder as f32 / 50.0 + 2.0) as u16
}

#[Cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_calcular_dano() {
        let resultado = calcular_dano(100, 50, 60);
        assert_eq!(resultado, 24, "El cálculo de daño es incorrecto.");
    }
}
  • #[cfg(test)]: Indica que este módulo solo se compila durante las pruebas.
  • #[test]: Marca la función como una prueba.
  • assert_eq!: Comprueba que el resultado es el esperado.

⚖️ Aserciones (assertions) comunes #

  • assert!: Verifica que una condición sea verdadera.
  • assert_eq!: Comprueba la igualdad entre valores.
  • assert_ne!: Comprueba que los valores no sean iguales.
  • assert_matches!: Verifica que un valor coincida con un patrón.

Ejemplo de varias aserciones: #

#[Test]
fn test_varias_aserciones() {
    let nivel = 42;
    assert!(nivel > 40, "El nivel es demasiado bajo.");
    assert_eq!(nivel, 42, "El nivel no coincide.");
    assert_ne!(nivel, 50, "El nivel es incorrecto.");
}

🔧 Pruebas con resultados #

Si una prueba devuelve Result<(), String>, podemos usar ? para propagar errores:

#[Test]
fn test_con_resultado() -> Result<(), String> {
    let valor = 10;
    if valor == 10 {
        Ok(())
    } else {
        Err(String::from("El valor no es 10"))
    }
}

⚔️ Pruebas de integración #

Las pruebas de integración validan que módulos o componentes funcionan correctamente juntos. Estas pruebas se colocan en la carpeta tests/.

Ejemplo: prueba de integración #

// tests/integration_test.rs
use mi_crate::calcular_dano;

#[Test]
fn test_dano_en_batalla() {
    let resultado = calcular_dano(120, 60, 80);
    assert_eq!(resultado, 34, "Daño inesperado en la batalla.");
}
  • tests/: Carpeta especial para pruebas de integración.
  • use mi_crate::: Importa el crate para probarlo desde fuera.

⌚ Ejecutando pruebas #

  • Ejecutar todas las pruebas: cargo test
  • Ejecutar pruebas con nombre específico: cargo test nombre_prueba
  • Ejecutar pruebas con resultados completos: cargo test -- --nocapture
  • Ignorar pruebas temporalmente: Agrega #[ignore].

Ejemplo de prueba ignorada #

#[Test]
#[Ignore]
fn test_lenta() {
    std::thread::sleep(std::time::Duration::from_secs(5));
    assert_eq!(2 + 2, 4);
}

Ejecuta solo las pruebas no ignoradas:

cargo test

Ejecuta también las pruebas ignoradas:

cargo test -- --ignored

⚛ Cobertura de pruebas #

Para medir la cobertura de pruebas, se puede usar cargo-tarpaulin:

cargo install cargo-tarpaulin
cargo tarpaulin --out Html

Esto generará un reporte detallado.

📊 Conclusión #

Escribir pruebas es fundamental para garantizar que nuestro código sea confiable. Rust facilita la creación de pruebas unitarias e integración, y proporciona herramientas para cubrir todos los aspectos del desarrollo seguro. Con estas prácticas, tu código será más resistente a errores y más fácil de mantener.

¡Atrapa todos los bugs antes de que aparezcan en producción!