En Rust, los tests unitarios se utilizan para verificar que pequeñas piezas de código (funciones o métodos) funcionen como se espera. Para que una función sea reconocida como un test por el motor de pruebas de Rust, debe estar marcada con el atributo #[test]. Estas funciones no reciben argumentos y deben retornar () o un tipo Result<(), E>. Si la función se ejecuta y termina sin errores, el test se marca como exitoso; si la ejecución provoca un “panic” (un error crítico), el test falla.

Para validar la lógica, utilizamos aserciones. assert!(condición) comprueba si una expresión es verdadera. Si necesitas comparar valores, assert_eq!(izquierda, derecha) verifica que ambos sean iguales y, en caso de error, muestra sus valores (siempre que el tipo implemente el trait Debug). Para verificar que dos valores son distintos, se utiliza assert_ne!(izquierda, derecha). Además, es posible pasar un mensaje de error personalizado a las aserciones para facilitar la depuración mediante el uso de macros de formato.

Cuando el comportamiento esperado de una función es que falle (haga panic), podemos usar el atributo #[should_panic]. Para mayor precisión, podemos usar #[should_panic(expected = "mensaje")] para asegurar que el panic contenga un texto específico. Finalmente, si un test es muy lento o pesado, se puede marcar con #[ignore] para que no se ejecute durante las pruebas rápidas habituales, pudiendo correrlo específicamente con cargo test -- --ignored.

Por buenas prácticas, los tests suelen organizarse dentro de un módulo dedicado en el mismo archivo, marcado con #[cfg(test)], lo que indica al compilador que ese código solo debe incluirse cuando se ejecuten las pruebas.

pub fn sumar(a: i32, b: i32) -> i32 {
    a + b
}

pub fn dividir(a: i32, b: i32) -> i32 {
    if b == 0 {
        panic!("error: división por cero");
    }
    a / b
}

#[cfg(test)]
mod tests {
    // Importamos todo desde el módulo padre para acceder a las funciones
    use super::*;

    #[test]
    fn test_sumar_exito() {
        // Verifica que 2 + 2 sea 4
        assert_eq!(sumar(2, 2), 4);
    }

    #[test]
    fn test_sumar_con_mensaje() {
        let resultado = sumar(10, 20);
        // assert! con mensaje personalizado si la condición es falsa
        assert!(resultado == 30, "El resultado esperado era 30, pero se obtuvo {}", resultado);
    }

    #[test]
    fn test_desigualdad() {
        // Verifica que los valores no sean iguales
        assert_ne!(sumar(2, 2), 5);
    }

    #[test]
    #[should_panic(expected = "error: división por cero")]
    fn test_dividir_error_panic() {
        // Este test pasa porque la función lanza un panic con el mensaje esperado
        dividir(10, 0);
    }

    #[test]
    #[ignore]
    fn test_lento() {
        // Este test se ignora por defecto con cargo test
        assert_eq!(1 + 1, 2);
    }
}

Explicación del Código

  • pub fn sumar(a: i32, b: i32) -> i32: Define la función sumar que devuelve un entero de 32 bits.
  • pub fn dividir(a: i32, b: i32) -> i32: Define dividir, la cual contiene un panic! si el divisor es cero.
  • #[cfg(test)]: Atributo que indica que el módulo tests solo se compilará cuando se ejecuten los tests.
  • use super::*;: Trae las funciones sumar y dividir al ámbito del módulo tests.
  • #[test]: Atributo que marca a test_sumar_exito, test_sumar_con_mensaje, test_desigualdad, test_dividir_error_panic y test_lento como funciones de prueba.
  • assert_eq!(sumar(2, 2), 4): Compara el retorno de sumar(2, 2) con 4; si no coinciden, el test falla.
  • assert!(resultado == 30, "..."): Valida que resultado sea 30. Si es falso, imprime el mensaje con el valor real de resultado.
  • assert_ne!(sumar(2, 2), 5): Asegura que el resultado de la suma no sea 5.
  • #[should_panic(expected = "...")]: Indica que test_dividir_error_panic solo es exitoso si la función dividir provoca un panic que contenga el texto "error: división por cero".
  • #[ignore]: Indica que test_lento no se ejecutará a menos que se use el flag --ignored en la terminal.

110