Pruebas de integración de Rust con Docker: de la A a la Z
\n\nLas pruebas de integración son una etapa críticamente importante en el desarrollo de aplicaciones confiables en Rust. A diferencia de las pruebas unitarias, las de integración verifican la interacción de los componentes con dependencias externas: bases de datos, cachés, colas de mensajes. Docker es ideal para crear entornos de prueba aislados y reproducibles. En este artículo, analizaremos cómo configurar las pruebas de integración de un proyecto Rust utilizando Docker, comenzando con la arquitectura básica y terminando con CI/CD.
\n\n1. Por qué Docker es la mejor opción para pruebas de integración de Rust
\n\nRust se compila a código nativo, pero sus aplicaciones a menudo dependen de servicios externos. Docker resuelve tres problemas clave:
\n- \n
- Aislamiento: cada prueba se ejecuta en un contenedor limpio, excluyendo la influencia de ejecuciones anteriores. \n
- Reproducibilidad: versiones exactas de PostgreSQL, Redis o Kafka garantizan el mismo comportamiento en la máquina local y en CI. \n
- Velocidad: las imágenes de Docker se almacenan en caché y las pruebas de Rust se compilan una sola vez. \n
Consideremos un proyecto típico: un servidor web en Actix-web que trabaja con PostgreSQL. Nuestro objetivo es escribir una prueba que levante un contenedor con la base de datos, ejecute migraciones y verifique las operaciones CRUD.
\n\n2. Configuración de la infraestructura de prueba con Docker Compose
\n\nPara mayor comodidad, usamos docker-compose.yml, que describe todas las dependencias. Creemos un archivo en la raíz del proyecto:
version: '3.8'\nservices:\n postgres_test:\n image: postgres:15-alpine\n environment:\n POSTGRES_USER: test_user\n POSTGRES_PASSWORD: test_pass\n POSTGRES_DB: test_db\n ports:\n - "5433:5432"\n tmpfs: /var/lib/postgresql/data # datos en memoria para velocidad\n\nObserve tmpfs — esto acelera las pruebas, ya que los datos no se escriben en el disco. El puerto 5433 se eligió para no entrar en conflicto con la base de datos local.
Ahora escribamos una función en Rust para gestionar los contenedores. Usamos el crate testcontainers — permite ejecutar contenedores Docker mediante programación:
// tests/common/mod.rs\nuse testcontainers::{clients, images::postgres::Postgres, Container};\n\npub struct TestDb {\n pub container: Container<'static, Postgres>,\n pub connection_string: String,\n}\n\nimpl TestDb {\n pub fn new() -> Self {\n let docker = clients::Cli::default();\n let postgres_image = Postgres::default();\n let container = docker.run(postgres_image);\n \n let host = container.get_host();\n let port = container.get_host_port_ipv4(5432);\n let connection_string = format!(\n "postgres://postgres:postgres@{}:{}/postgres",\n host, port\n );\n \n TestDb { container, connection_string }\n }\n}\n\nEste módulo se puede reutilizar en todas las pruebas de integración. El contenedor se eliminará automáticamente si las pruebas fallan o al finalizar el proceso.
\n\n3. Escritura de pruebas de integración con solicitudes reales
\n\nCreemos una prueba que verifique la inserción y lectura de un usuario. Supongamos que tenemos una estructura User y las funciones create_user y get_user en el código principal.
// tests/integration_test.rs\nmod common;\n\nuse sqlx::PgPool;\nuse your_crate::{create_user, get_user, User};\n\n#[tokio::test]\nasync fn test_create_and_get_user() {\n let test_db = common::TestDb::new();\n let pool = PgPool::connect(&test_db.connection_string)\n .await\n .expect("Failed to connect to DB");\n \n // Ejecutamos migraciones (si usas sqlx)\n sqlx::migrate!().run(&pool).await.unwrap();\n \n // Creamos un usuario\n let user = User {\n name: "Alice".into(),\n email: "alice@example.com".into(),\n };\n let created = create_user(&pool, &us