rust-tests-integracion

Tests de integración en Rust con tests y API pública

  • 3 min

Un test de integración es una prueba externa de la API pública de una librería.

Los tests unitarios (vistos en el artículo anterior) viven en el mismo archivo que el código. Tienen una ventaja muy concreta: pueden ver y probar funciones privadas.

Los Tests de Integración son distintos. Viven fuera de tu código fuente. Rust los trata exactamente igual que si fueran un código externo (otra Crate) que está importando tu librería.

  • Solo pueden llamar a funciones pub.
  • Verifican que la API pública sea cómoda y funcione correctamente.
  • Si cambias la implementación interna pero no la API, estos tests no deberían romperse.

La carpeta tests/

Para crear tests de integración, necesitamos una carpeta específica al mismo nivel que src.

mi_proyecto/
├── Cargo.toml
├── src/
│   └── lib.rs
└── tests/           <--- AQUÍ
    └── integracion.rs
Copied!

Cargo sabe automáticamente que todo archivo .rs dentro de tests/ es un test de integración. No hace falta configurarlo.

Escribir el primer test de integración

Supongamos que nuestra librería (src/lib.rs) tiene esto:

// src/lib.rs
pub fn sumar_dos(a: i32) -> i32 {
    sumar_interno(a, 2)
}

// Función privada (no accesible desde tests de integración)
fn sumar_interno(a: i32, b: i32) -> i32 {
    a + b
}
Copied!

Ahora creamos el archivo tests/mi_prueba.rs:

// tests/mi_prueba.rs

// 1. Debemos importar nuestra librería como si fuera externa "use nombre_crate"
use mi_proyecto;

#[test]
fn prueba_api_publica() {
    // Solo podemos acceder a lo que sea 'pub'
    assert_eq!(mi_proyecto::sumar_dos(2), 4);
}
Copied!

Al ejecutar cargo test, verás una nueva sección:

Running tests/mi_prueba.rs (target/debug/deps/mi_prueba-...)
test prueba_api_publica ... ok
Copied!

Diferencias clave

  1. No necesitamos #[cfg(test)]: Los archivos en tests/ solo se compilan cuando ejecutamos tests, así que no hace falta la anotación de módulo.
  2. Cada archivo es una Crate: Rust compila cada archivo dentro de tests/ como si fuera un binario independiente. Esto es importante para entender cómo compartir código.

Compartir código de preparación

Imagina que tienes 5 archivos de tests y todos necesitan una función de configuración (por ejemplo, crear_usuario_prueba()).

Si creas un archivo tests/common.rs, Rust pensará que es otro test e intentará ejecutarlo, lo cual no queremos.

La forma correcta de hacerlo es usando la estructura de módulos de Rust: Crear una subcarpeta.

Estructura de archivos:

tests/
├── prueba_login.rs
└── common/
    └── mod.rs       <--- Código de ayuda
Copied!

Al ponerlo en common/mod.rs, Rust no lo considera un test independiente, sino un módulo que otros tests pueden importar.

En tests/common/mod.rs:

pub fn setup() {
    // Código para preparar base de datos, etc.
    println!("Configurando entorno de pruebas...");
}
Copied!

En tests/prueba_login.rs:

use mi_proyecto;
mod common; // Importamos el módulo helper

#[test]
fn login_funciona() {
    common::setup(); // Llamamos a la configuración
    assert!(true);
}
Copied!

Probar binarios (main.rs)

Aquí hay una pequeña limitación de Rust: Los tests de integración están diseñados para probar Librerías (lib.rs). Si tu proyecto es solo un binario (src/main.rs), no puedes hacer use mi_proyecto desde la carpeta tests/, porque los binarios no exponen funciones.

Una estructura habitual: Si el programa crece, podemos mover la lógica a lib.rs y dejar main.rs como una capa pequeña que llama a la librería.

// src/main.rs
use mi_proyecto::arrancar;

fn main() {
    // El main es tan simple que no necesita test
    arrancar();
}
Copied!

Así puedes testear toda la lógica de arrancar y sus componentes mediante tests de integración en la librería.