Fundamentos de Pruebas Unitarias en Rust

RustRustBeginner
Practicar Ahora

This tutorial is from open-source community. Access the source code

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

En este laboratorio, aprendemos sobre las pruebas unitarias en Rust. Las pruebas unitarias son funciones de Rust que verifican el código no de prueba mediante la realización de configuraciones, la ejecución del código y la afirmación de los resultados. Estas pruebas se escriben en un módulo tests con el atributo #[cfg(test)] y se marcan con el atributo #[test]. Las pruebas pueden fallar si algo en la función de prueba produce un panic, y se utilizan macros auxiliares como assert!, assert_eq! y assert_ne! para las afirmaciones. Rust 2018 permite que las pruebas unitarias devuelvan Result<()> para utilizar el operador ? para una prueba concisa. También hay soporte para probar panics utilizando el atributo #[should_panic]. Se pueden ejecutar pruebas específicas utilizando el nombre de la prueba con el comando cargo test, y se pueden ignorar las pruebas utilizando el atributo #[ignore] o ejecutando cargo test -- --ignored.

Nota: Si el laboratorio no especifica un nombre de archivo, puede utilizar cualquier nombre de archivo que desee. Por ejemplo, puede utilizar main.rs, compilar y ejecutarlo con rustc main.rs &&./main.

Pruebas unitarias

Las pruebas son funciones de Rust que verifican que el código no de prueba funcione de la manera esperada. Los cuerpos de las funciones de prueba generalmente realizan alguna configuración, ejecutan el código que queremos probar y luego afirman si los resultados son los que esperamos.

La mayoría de las pruebas unitarias van en un módulo tests con el atributo #[cfg(test)]. Las funciones de prueba se marcan con el atributo #[test].

Las pruebas fallan cuando algo en la función de prueba produce un panic. Hay algunas macros auxiliares:

  • assert!(expresión) - produce un panic si la expresión evalúa a false.
  • assert_eq!(izquierda, derecha) y assert_ne!(izquierda, derecha) - prueban la igualdad y desigualdad de las expresiones izquierda y derecha respectivamente.
pub fn add(a: i32, b: i32) -> i32 {
    a + b
}

// Esta es una función de suma muy mala, su propósito es fallar en este
// ejemplo.
#[allow(dead_code)]
fn bad_add(a: i32, b: i32) -> i32 {
    a - b
}

#[cfg(test)]
mod tests {
    // Observe esta práctica útil: importar nombres del ámbito externo (para las pruebas de módulo).
    use super::*;

    #[test]
    fn test_add() {
        assert_eq!(add(1, 2), 3);
    }

    #[test]
    fn test_bad_add() {
        // Esta afirmación se activará y la prueba fallará.
        // Tenga en cuenta que las funciones privadas también se pueden probar!
        assert_eq!(bad_add(1, 2), 3);
    }
}

Las pruebas se pueden ejecutar con cargo test.

$ cargo test

running 2 tests
test tests::test_bad_add... FAILED
test tests::test_add... ok

fallures:

---- tests::test_bad_add stdout ----
thread 'tests::test_bad_add' panicked at 'assertion failed: `(left == right)`
  left: `-1`,
 right: `3`', src/lib.rs:21:8
note: Run with $(RUST_BACKTRACE=1) for a backtrace.

fallures:
tests::test_bad_add

test result: FAILED. 1 passed
1 failed
0 ignored
0 measured
0 filtered out

Pruebas y ?

Ninguno de los ejemplos de pruebas unitarias anteriores tenía un tipo de retorno. Pero en Rust 2018, sus pruebas unitarias pueden devolver Result<()>, lo que le permite utilizar ? en ellas. Esto puede hacerlas mucho más concisas.

fn sqrt(number: f64) -> Result<f64, String> {
    if number >= 0.0 {
        Ok(number.powf(0.5))
    } else {
        Err("negative floats don't have square roots".to_owned())
    }
}

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

    #[test]
    fn test_sqrt() -> Result<(), String> {
        let x = 4.0;
        assert_eq!(sqrt(x)?.powf(2.0), x);
        Ok(())
    }
}

Vea "The Edition Guide" para obtener más detalles.

Pruebas de panic

Para comprobar funciones que deben producir un panic en ciertas circunstancias, use el atributo #[should_panic]. Este atributo acepta un parámetro opcional expected = con el texto del mensaje de panic. Si su función puede producir un panic de múltiples maneras, ayuda a asegurarse de que su prueba esté probando el panic correcto.

pub fn divide_non_zero_result(a: u32, b: u32) -> u32 {
    if b == 0 {
        panic!("Divide-by-zero error");
    } else if a < b {
        panic!("Divide result is zero");
    }
    a / b
}

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

    #[test]
    fn test_divide() {
        assert_eq!(divide_non_zero_result(10, 2), 5);
    }

    #[test]
    #[should_panic]
    fn test_any_panic() {
        divide_non_zero_result(1, 0);
    }

    #[test]
    #[should_panic(expected = "Divide result is zero")]
    fn test_specific_panic() {
        divide_non_zero_result(1, 10);
    }
}

Ejecutar estas pruebas nos da:

$ cargo test

running 3 tests
test tests::test_any_panic... ok
test tests::test_divide... ok
test tests::test_specific_panic... ok

test result: ok. 3 passed
0 failed
0 ignored
0 measured
0 filtered out

Doc-tests tmp-test-should-panic

running 0 tests

test result: ok. 0 passed
0 failed
0 ignored
0 measured
0 filtered out

Ejecutar pruebas específicas

Para ejecutar pruebas específicas, se puede especificar el nombre de la prueba en el comando cargo test.

$ cargo test test_any_panic
running 1 test
test tests::test_any_panic... ok

test result: ok. 1 passed
0 failed
0 ignored
0 measured
2 filtered out

Doc-tests tmp-test-should-panic

running 0 tests

test result: ok. 0 passed
0 failed
0 ignored
0 measured
0 filtered out

Para ejecutar múltiples pruebas, se puede especificar una parte del nombre de la prueba que coincida con todas las pruebas que deben ejecutarse.

$ cargo test panic
running 2 tests
test tests::test_any_panic... ok
test tests::test_specific_panic... ok

test result: ok. 2 passed
0 failed
0 ignored
0 measured
1 filtered out

Doc-tests tmp-test-should-panic

running 0 tests

test result: ok. 0 passed
0 failed
0 ignored
0 measured
0 filtered out

Ignorar pruebas

Las pruebas se pueden marcar con el atributo #[ignore] para excluir algunas pruebas. O para ejecutarlas con el comando cargo test -- --ignored

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

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

    #[test]
    fn test_add() {
        assert_eq!(add(2, 2), 4);
    }

    #[test]
    fn test_add_hundred() {
        assert_eq!(add(100, 2), 102);
        assert_eq!(add(2, 100), 102);
    }

    #[test]
    #[ignore]
    fn ignored_test() {
        assert_eq!(add(0, 0), 0);
    }
}
$ cargo test
running 3 tests
test tests::ignored_test... ignored
test tests::test_add... ok
test tests::test_add_hundred... ok

test result: ok. 2 passed
0 failed
1 ignored
0 measured
0 filtered out

Doc-tests tmp-ignore

running 0 tests

test result: ok. 0 passed
0 failed
0 ignored
0 measured
0 filtered out

$ cargo test -- --ignored
running 1 test
test tests::ignored_test... ok

test result: ok. 1 passed
0 failed
0 ignored
0 measured
0 filtered out

Doc-tests tmp-ignore

running 0 tests

test result: ok. 0 passed
0 failed
0 ignored
0 measured
0 filtered out

Resumen

¡Felicitaciones! Has completado el laboratorio de Pruebas Unitarias. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.