Laboratorio del Libro de Rust: Pruebas Unitarias e Integrales

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

Bienvenido a Test Organization. Esta práctica es parte del Rust Book. Puedes practicar tus habilidades de Rust en LabEx.

En esta práctica, aprenderemos sobre las dos principales categorías de pruebas en la comunidad de Rust: las pruebas unitarias, que son pequeñas y se centran en probar módulos individuales de manera aislada, y las pruebas de integración, que utilizan la interfaz pública de la biblioteca y pueden probar múltiples módulos por cada prueba.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/MemorySafetyandManagementGroup(["Memory Safety and Management"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/ProjectManagementandOrganizationGroup(["Project Management and Organization"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/DataTypesGroup -.-> rust/integer_types("Integer Types") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/MemorySafetyandManagementGroup -.-> rust/lifetime_specifiers("Lifetime Specifiers") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/ProjectManagementandOrganizationGroup -.-> rust/module_system("Module System") subgraph Lab Skills rust/variable_declarations -.-> lab-100417{{"Laboratorio del Libro de Rust: Pruebas Unitarias e Integrales"}} rust/integer_types -.-> lab-100417{{"Laboratorio del Libro de Rust: Pruebas Unitarias e Integrales"}} rust/function_syntax -.-> lab-100417{{"Laboratorio del Libro de Rust: Pruebas Unitarias e Integrales"}} rust/expressions_statements -.-> lab-100417{{"Laboratorio del Libro de Rust: Pruebas Unitarias e Integrales"}} rust/lifetime_specifiers -.-> lab-100417{{"Laboratorio del Libro de Rust: Pruebas Unitarias e Integrales"}} rust/method_syntax -.-> lab-100417{{"Laboratorio del Libro de Rust: Pruebas Unitarias e Integrales"}} rust/module_system -.-> lab-100417{{"Laboratorio del Libro de Rust: Pruebas Unitarias e Integrales"}} end

Organización de las Pruebas

Como se mencionó al principio del capítulo, la prueba es una disciplina compleja y diferentes personas utilizan diferentes terminologías y organizaciones. La comunidad de Rust piensa en las pruebas en términos de dos categorías principales: pruebas unitarias e integración. Las pruebas unitarias son pequeñas y más enfocadas, probando un módulo aisladamente a la vez y pueden probar interfaces privadas. Las pruebas de integración son completamente externas a su biblioteca y utilizan su código de la misma manera que cualquier otro código externo, utilizando solo la interfaz pública y posiblemente probando múltiples módulos por cada prueba.

Es importante escribir ambos tipos de pruebas para asegurarse de que las piezas de su biblioteca estén haciendo lo que se espera que hagan, por separado y juntas.

Pruebas Unitarias

El propósito de las pruebas unitarias es probar cada unidad de código de manera aislada del resto del código para localizar rápidamente dónde el código está y no está funcionando como se esperaba. Colocará las pruebas unitarias en el directorio src en cada archivo con el código que están probando. La convención es crear un módulo llamado tests en cada archivo para contener las funciones de prueba y anotar el módulo con cfg(test).

El Módulo de Pruebas y #[cfg(test)]

La anotación #[cfg(test)] en el módulo tests le dice a Rust que compile y ejecute el código de prueba solo cuando ejecuta cargo test, no cuando ejecuta cargo build. Esto ahorra tiempo de compilación cuando solo desea compilar la biblioteca y ahorra espacio en el artefacto compilado resultante porque las pruebas no se incluyen. Verá que debido a que las pruebas de integración se encuentran en un directorio diferente, no necesitan la anotación #[cfg(test)]. Sin embargo, debido a que las pruebas unitarias se encuentran en los mismos archivos que el código, utilizará #[cfg(test)] para especificar que no deben incluirse en el resultado compilado.

Recuerde que cuando generamos el nuevo proyecto adder en la primera sección de este capítulo, Cargo generó este código para nosotros:

Nombre de archivo: src/lib.rs

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

Este código es el módulo tests generado automáticamente. El atributo cfg significa configuración y le dice a Rust que el siguiente elemento solo debe incluirse dada una cierta opción de configuración. En este caso, la opción de configuración es test, que es proporcionada por Rust para compilar y ejecutar pruebas. Al utilizar el atributo cfg, Cargo compila nuestro código de prueba solo si activamente ejecutamos las pruebas con cargo test. Esto incluye cualquier función auxiliar que pueda estar dentro de este módulo, además de las funciones anotadas con #[test].

Probando Funciones Privadas

Existe un debate dentro de la comunidad de pruebas sobre si las funciones privadas deben ser probadas directamente, y en otros lenguajes es difícil o imposible probar funciones privadas. Independientemente de la ideología de pruebas a la que adhieres, las reglas de privacidad de Rust te permiten probar funciones privadas. Considere el código de la Lista 11-12 con la función privada internal_adder.

Nombre de archivo: src/lib.rs

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

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

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

    #[test]
    fn internal() {
        assert_eq!(4, internal_adder(2, 2));
    }
}

Lista 11-12: Probando una función privada

Tenga en cuenta que la función internal_adder no está marcada como pub. Las pruebas son simplemente código de Rust, y el módulo tests es simplemente otro módulo. Como discutimos en "Rutas para Referirse a un Elemento en el Árbol de Módulos", los elementos en los módulos hijos pueden usar los elementos en sus módulos ancestros. En esta prueba, traemos todos los elementos del padre del módulo test al alcance con use super::*, y luego la prueba puede llamar a internal_adder. Si no piensas que las funciones privadas deben ser probadas, en Rust no hay nada que te obligue a hacerlo.

Pruebas de Integración

En Rust, las pruebas de integración son completamente externas a su biblioteca. Las utilizan de la misma manera que cualquier otro código, lo que significa que solo pueden llamar a funciones que forman parte de la API pública de su biblioteca. Su propósito es probar si muchas partes de su biblioteca funcionan correctamente juntas. Las unidades de código que funcionan correctamente por sí mismas pueden tener problemas cuando se integran, por lo que la cobertura de pruebas del código integrado también es importante. Para crear pruebas de integración, primero necesita un directorio tests.

El Directorio tests

Creamos un directorio tests en el nivel superior de nuestro directorio de proyecto, junto a src. Cargo sabe buscar archivos de prueba de integración en este directorio. Luego podemos crear tantos archivos de prueba como queramos, y Cargo compilará cada archivo como un crat individual.

Vamos a crear una prueba de integración. Con el código de la Lista 11-12 todavía en el archivo src/lib.rs, cree un directorio tests y cree un nuevo archivo llamado tests/integration_test.rs. Su estructura de directorios debería verse así:

adder
├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    └── integration_test.rs

Ingrese el código de la Lista 11-13 en el archivo tests/integration_test.rs.

Nombre de archivo: tests/integration_test.rs

use adder;

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

Lista 11-13: Una prueba de integración de una función en el crat adder

Cada archivo en el directorio tests es un crat separado, por lo que necesitamos traer nuestra biblioteca al alcance de cada crat de prueba. Por eso agregamos use adder; al principio del código, lo que no necesitamos en las pruebas unitarias.

No necesitamos anotar ningún código en tests/integration_test.rs con #[cfg(test)]. Cargo trata el directorio tests de manera especial y solo compila los archivos de este directorio cuando ejecutamos cargo test. Ejecute cargo test ahora:

[object Object]

Las tres secciones de salida incluyen las pruebas unitarias, la prueba de integración y las pruebas de documentación. Tenga en cuenta que si alguna prueba en una sección falla, no se ejecutarán las siguientes secciones. Por ejemplo, si una prueba unitaria falla, no habrá ninguna salida para las pruebas de integración y de documentación porque esas pruebas solo se ejecutarán si todas las pruebas unitarias pasan.

La primera sección para las pruebas unitarias [1] es la misma que la que hemos estado viendo: una línea para cada prueba unitaria (una llamada internal que agregamos en la Lista 11-12) y luego una línea resumen para las pruebas unitarias.

La sección de pruebas de integración comienza con la línea Running tests/integration_test.rs [2]. A continuación, hay una línea para cada función de prueba en esa prueba de integración [3] y una línea resumen para los resultados de la prueba de integración [4] justo antes de que comience la sección Doc-tests adder.

Cada archivo de prueba de integración tiene su propia sección, por lo que si agregamos más archivos en el directorio tests, habrá más secciones de prueba de integración.

Todavía podemos ejecutar una función de prueba de integración específica especificando el nombre de la función de prueba como argumento para cargo test. Para ejecutar todas las pruebas en un archivo de prueba de integración particular, use el argumento --test de cargo test seguido del nombre del archivo:

[object Object]

Este comando solo ejecuta las pruebas en el archivo tests/integration_test.rs.

Submódulos en Pruebas de Integración

A medida que agregas más pruebas de integración, es posible que desees crear más archivos en el directorio tests para ayudar a organizarlos; por ejemplo, puedes agrupar las funciones de prueba por la funcionalidad que están probando. Como se mencionó anteriormente, cada archivo en el directorio tests se compila como un crat separado, lo que es útil para crear ámbitos separados y imitar más de cerca la forma en que los usuarios finales utilizarán tu crat. Sin embargo, esto significa que los archivos en el directorio tests no comparten el mismo comportamiento que los archivos en src, como aprendiste en el Capítulo 7 sobre cómo separar el código en módulos y archivos.

El comportamiento diferente de los archivos del directorio tests es más evidente cuando tienes un conjunto de funciones auxiliares para usar en múltiples archivos de prueba de integración y tratas de seguir los pasos de "Separando Módulos en Diferentes Archivos" para extraerlas a un módulo común. Por ejemplo, si creamos tests/common.rs y ponemos una función llamada setup en él, podemos agregar un poco de código a setup que queramos llamar desde múltiples funciones de prueba en múltiples archivos de prueba:

Nombre de archivo: tests/common.rs

pub fn setup() {
    // código de configuración específico de las pruebas de tu biblioteca iría aquí
}

Cuando ejecutamos las pruebas nuevamente, veremos una nueva sección en la salida de las pruebas para el archivo common.rs, aunque este archivo no contiene ninguna función de prueba ni llamamos a la función setup desde ningún lugar:

running 1 test
test tests::internal... ok

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

     Running tests/common.rs (target/debug/deps/common-
92948b65e88960b4)

running 0 tests

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

     Running tests/integration_test.rs
(target/debug/deps/integration_test-92948b65e88960b4)

running 1 test
test it_adds_two... ok

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

   Doc-tests adder

running 0 tests

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

Que common aparezca en los resultados de las pruebas con running 0 tests mostrado para él no es lo que queríamos. Solo queríamos compartir un poco de código con los otros archivos de prueba de integración. Para evitar que common aparezca en la salida de las pruebas, en lugar de crear tests/common.rs, crearemos tests/common/mod.rs. El directorio del proyecto ahora se ve así:

├── Cargo.lock
├── Cargo.toml
├── src
│   └── lib.rs
└── tests
    ├── common
    │   └── mod.rs
    └── integration_test.rs

Esta es la antigua convención de nombres que Rust también entiende que mencionamos en "Rutas Alternativas de Archivos". Nombra el archivo de esta manera para decirle a Rust que no trate el módulo common como un archivo de prueba de integración. Cuando movemos el código de la función setup a tests/common/mod.rs y eliminamos el archivo tests/common.rs, la sección en la salida de las pruebas ya no aparecerá. Los archivos en subdirectorios del directorio tests no se compilan como crates separados ni tienen secciones en la salida de las pruebas.

Después de crear tests/common/mod.rs, podemos usarlo desde cualquiera de los archivos de prueba de integración como un módulo. Aquí hay un ejemplo de llamar a la función setup desde la prueba it_adds_two en tests/integration_test.rs:

Nombre de archivo: tests/integration_test.rs

use adder;

mod common;

#[test]
fn it_adds_two() {
    common::setup();
    assert_eq!(4, adder::add_two(2));
}

Tenga en cuenta que la declaración mod common; es la misma que la declaración de módulo que demostramos en la Lista 7-21. Luego, en la función de prueba, podemos llamar a la función common::setup().

Pruebas de Integración para Crat Binarios

Si nuestro proyecto es un crat binario que solo contiene un archivo src/main.rs y no tiene un archivo src/lib.rs, no podemos crear pruebas de integración en el directorio tests y traer las funciones definidas en el archivo src/main.rs al alcance con una declaración use. Solo los crates de biblioteca exponen funciones que otros crates pueden usar; los crates binarios están destinados a ser ejecutados por sí mismos.

Esta es una de las razones por las que los proyectos de Rust que proporcionan un binario tienen un archivo src/main.rs sencillo que llama a la lógica que reside en el archivo src/lib.rs. Utilizando esa estructura, las pruebas de integración pueden probar el crat de biblioteca con use para hacer disponible la funcionalidad importante. Si la funcionalidad importante funciona, el pequeño código en el archivo src/main.rs también funcionará, y ese pequeño código no necesita ser probado.

Resumen

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