Controlar cómo se ejecutan las pruebas

Beginner

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

Introducción

Bienvenido a Controlling How Tests Are Run (Controlar cómo se ejecutan las pruebas). Esta práctica es parte del Rust Book. Puedes practicar tus habilidades de Rust en LabEx.

En esta práctica, aprenderás a controlar el comportamiento de las ejecuciones de pruebas en Rust utilizando opciones de línea de comandos para cargo test y el binario de prueba resultante.

Controlar cómo se ejecutan las pruebas

Al igual que cargo run compila tu código y luego ejecuta el binario resultante, cargo test compila tu código en modo de prueba y ejecuta el binario de prueba resultante. El comportamiento predeterminado del binario producido por cargo test es ejecutar todas las pruebas en paralelo y capturar la salida generada durante las ejecuciones de prueba, evitando que se muestre la salida y facilitando la lectura de la salida relacionada con los resultados de las pruebas. Sin embargo, puedes especificar opciones de línea de comandos para cambiar este comportamiento predeterminado.

Algunas opciones de línea de comandos van a cargo test, y otras van al binario de prueba resultante. Para separar estos dos tipos de argumentos, listas los argumentos que van a cargo test seguidos del separador -- y luego los que van al binario de prueba. Ejecutar cargo test --help muestra las opciones que puedes utilizar con cargo test, y ejecutar cargo test -- --help muestra las opciones que puedes utilizar después del separador.

Ejecutar pruebas en paralelo o secuencialmente

Cuando ejecutas múltiples pruebas, por defecto se ejecutan en paralelo utilizando hilos, lo que significa que terminan de ejecutarse más rápido y obtienes retroalimentación más rápidamente. Debido a que las pruebas se ejecutan al mismo tiempo, debes asegurarte de que tus pruebas no dependan una de otra ni de ningún estado compartido, incluyendo un entorno compartido, como el directorio de trabajo actual o las variables de entorno.

Por ejemplo, supongamos que cada una de tus pruebas ejecuta un código que crea un archivo en el disco llamado test-output.txt y escribe algunos datos en ese archivo. Luego, cada prueba lee los datos en ese archivo y afirma que el archivo contiene un valor particular, que es diferente en cada prueba. Debido a que las pruebas se ejecutan al mismo tiempo, una prueba podría sobrescribir el archivo en el intervalo de tiempo entre que otra prueba escribe y lee el archivo. La segunda prueba entonces fallará, no porque el código sea incorrecto sino porque las pruebas se han interferido entre sí mientras se ejecutan en paralelo. Una solución es asegurarse de que cada prueba escriba en un archivo diferente; otra solución es ejecutar las pruebas una a la vez.

Si no quieres ejecutar las pruebas en paralelo o si quieres un control más detallado sobre el número de hilos utilizados, puedes enviar la bandera --test-threads y el número de hilos que quieres utilizar al binario de prueba. Echa un vistazo al siguiente ejemplo:

cargo test -- --test-threads=1

Establecemos el número de hilos de prueba en 1, lo que le dice al programa que no use ningún paralelismo. Ejecutar las pruebas con un solo hilo tardará más tiempo que ejecutarlas en paralelo, pero las pruebas no se interferirán entre sí si comparten estado.

Mostrar la salida de una función

Por defecto, si una prueba pasa, la biblioteca de pruebas de Rust captura cualquier cosa impresa en la salida estándar. Por ejemplo, si llamamos a println! en una prueba y la prueba pasa, no veremos la salida de println! en la terminal; solo veremos la línea que indica que la prueba ha pasado. Si una prueba falla, veremos todo lo que se haya impreso en la salida estándar junto con el resto del mensaje de error.

Como ejemplo, en la Lista 11-10 hay una función tontería que imprime el valor de su parámetro y devuelve 10, así como una prueba que pasa y una prueba que falla.

Nombre del archivo: src/lib.rs

fn prints_and_returns_10(a: i32) -> i32 {
    println!("I got the value {a}");
    10
}

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

    #[test]
    fn this_test_will_pass() {
        let value = prints_and_returns_10(4);
        assert_eq!(10, value);
    }

    #[test]
    fn this_test_will_fail() {
        let value = prints_and_returns_10(8);
        assert_eq!(5, value);
    }
}

Lista 11-10: Pruebas para una función que llama a println!

Cuando ejecutamos estas pruebas con cargo test, veremos la siguiente salida:

running 2 tests
test tests::this_test_will_pass... ok
test tests::this_test_will_fail... FAILED

failures:

---- tests::this_test_will_fail stdout ----
1 I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace

failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s

Tenga en cuenta que en ninguna parte de esta salida vemos I got the value 4, que se imprime cuando se ejecuta la prueba que pasa. Esa salida ha sido capturada. La salida de la prueba que falló, I got the value 8 [1], aparece en la sección de la salida resumida de la prueba, que también muestra la causa del fallo de la prueba.

Si también queremos ver los valores impresos para las pruebas que pasan, podemos decirle a Rust que muestre también la salida de las pruebas exitosas con --show-output:

cargo test -- --show-output

Cuando ejecutamos de nuevo las pruebas de la Lista 11-10 con la bandera --show-output, vemos la siguiente salida:

running 2 tests
test tests::this_test_will_pass... ok
test tests::this_test_will_fail... FAILED

successes:

---- tests::this_test_will_pass stdout ----
I got the value 4


successes:
    tests::this_test_will_pass

failures:

---- tests::this_test_will_fail stdout ----
I got the value 8
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `5`,
 right: `10`', src/lib.rs:19:9
note: run with `RUST_BACKTRACE=1` environment variable to display
a backtrace

failures:
    tests::this_test_will_fail

test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s

Ejecutar un subconjunto de pruebas por nombre

A veces, ejecutar una suite completa de pruebas puede tomar mucho tiempo. Si estás trabajando en código en un área particular, es posible que desees ejecutar solo las pruebas relacionadas con ese código. Puedes elegir qué pruebas ejecutar pasando a cargo test el nombre o nombres de la(s) prueba(s) que quieres ejecutar como argumento.

Para demostrar cómo ejecutar un subconjunto de pruebas, primero crearemos tres pruebas para nuestra función add_two, como se muestra en la Lista 11-11, y luego elegiremos cuáles ejecutar.

Nombre del archivo: src/lib.rs

pub fn add_two(a: i32) -> i32 {
    a + 2
}

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

    #[test]
    fn add_two_and_two() {
        assert_eq!(4, add_two(2));
    }

    #[test]
    fn add_three_and_two() {
        assert_eq!(5, add_two(3));
    }

    #[test]
    fn one_hundred() {
        assert_eq!(102, add_two(100));
    }
}

Lista 11-11: Tres pruebas con tres nombres diferentes

Si ejecutamos las pruebas sin pasar ningún argumento, como vimos anteriormente, todas las pruebas se ejecutarán en paralelo:

running 3 tests
test tests::add_three_and_two... ok
test tests::add_two_and_two... ok
test tests::one_hundred... ok

test result: ok. 3 passed; 0 failed; 0 ignored; 0 measured; 0
filtered out; finished in 0.00s

Ejecutar una sola prueba

Podemos pasar el nombre de cualquier función de prueba a cargo test para ejecutar solo esa prueba:

[object Object]

Solo se ejecutó la prueba con el nombre one_hundred; las otras dos pruebas no coincidían con ese nombre. La salida de la prueba nos informa de que teníamos más pruebas que no se ejecutaron al mostrar 2 filtradas al final.

No podemos especificar los nombres de múltiples pruebas de esta manera; solo se usará el primer valor dado a cargo test. Pero hay una forma de ejecutar múltiples pruebas.

Filtrado para ejecutar múltiples pruebas

Podemos especificar una parte del nombre de una prueba, y cualquier prueba cuyo nombre coincida con ese valor se ejecutará. Por ejemplo, dado que dos de los nombres de nuestras pruebas contienen add, podemos ejecutarlas dos ejecutando cargo test add:

[object Object]

Este comando ejecutó todas las pruebas con add en el nombre y filtró la prueba llamada one_hundred. También tenga en cuenta que el módulo en el que aparece una prueba se convierte en parte del nombre de la prueba, por lo que podemos ejecutar todas las pruebas en un módulo filtrando por el nombre del módulo.

Ignorar algunas pruebas a menos que se soliciten específicamente

A veces, algunas pruebas específicas pueden ser muy tardadas en ejecutarse, por lo que es posible que desees excluirlas durante la mayoría de las ejecuciones de cargo test. En lugar de enumerar como argumentos todas las pruebas que realmente quieres ejecutar, en su lugar puedes anotar las pruebas tardadas utilizando el atributo ignore para excluirlas, como se muestra aquí:

Nombre del archivo: src/lib.rs

#[test]
fn it_works() {
    let result = 2 + 2;
    assert_eq!(result, 4);
}

#[test]
#[ignore]
fn expensive_test() {
    // código que tarda una hora en ejecutarse
}

Después de #[test], agregamos la línea #[ignore] a la prueba que queremos excluir. Ahora, cuando ejecutamos nuestras pruebas, it_works se ejecuta, pero expensive_test no:

[object Object]

La función expensive_test se lista como ignored. Si queremos ejecutar solo las pruebas ignoradas, podemos usar cargo test -- --ignored:

[object Object]

Al controlar qué pruebas se ejecutan, puedes asegurarte de que los resultados de cargo test se devuelvan rápidamente. Cuando llegues a un punto en el que tenga sentido comprobar los resultados de las pruebas ignored y tengas tiempo para esperar los resultados, puedes ejecutar cargo test -- --ignored en su lugar. Si quieres ejecutar todas las pruebas, ya sean ignoradas o no, puedes ejecutar cargo test -- --include-ignored.

Resumen

¡Felicitaciones! Has completado el laboratorio de Controlar cómo se ejecutan las pruebas. Puedes practicar más laboratorios en LabEx para mejorar tus habilidades.