Integrationstests von Rust mit Docker: Von A bis Z
\n\nIntegrationstests sind ein kritisch wichtiger Schritt bei der Entwicklung zuverlässiger Anwendungen in Rust. Im Gegensatz zu Modultests überprüfen Integrationstests die Interaktion von Komponenten mit externen Abhängigkeiten: Datenbanken, Caches, Nachrichtenwarteschlangen. Docker eignet sich ideal zur Erstellung isolierter, reproduzierbarer Testumgebungen. In diesem Artikel werden wir erläutern, wie man Integrationstests für ein Rust-Projekt mit Docker einrichtet, von der grundlegenden Architektur bis hin zu CI/CD.
\n\n1. Warum Docker die beste Wahl für Integrationstests von Rust ist
\n\nRust wird in nativen Code kompiliert, aber seine Anwendungen hängen oft von externen Diensten ab. Docker löst drei zentrale Probleme:
\n- \n
- Isolation: Jeder Test wird in einem sauberen Container gestartet, wodurch Einflüsse vorheriger Läufe ausgeschlossen werden. \n
- Reproduzierbarkeit: Exakte Versionen von PostgreSQL, Redis oder Kafka gewährleisten ein identisches Verhalten auf dem lokalen Rechner und in CI. \n
- Geschwindigkeit: Docker-Images werden gecacht, und Rust-Tests werden nur einmal kompiliert. \n
Betrachten wir ein typisches Projekt: Ein Webserver mit Actix-web, der mit PostgreSQL arbeitet. Unser Ziel ist es, einen Test zu schreiben, der einen Container mit der Datenbank startet, Migrationen durchführt und CRUD-Operationen überprüft.
\n\n2. Einrichtung der Testinfrastruktur mit Docker Compose
\n\nDer Einfachheit halber verwenden wir docker-compose.yml, das alle Abhängigkeiten beschreibt. Erstellen wir die Datei im Projektstammverzeichnis:
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 # Daten im Speicher für Geschwindigkeit\n\nBeachten Sie tmpfs – dies beschleunigt die Tests, da Daten nicht auf die Festplatte geschrieben werden. Port 5433 wurde gewählt, um Konflikte mit der lokalen Datenbank zu vermeiden.
Jetzt schreiben wir eine Rust-Funktion zur Verwaltung der Container. Wir verwenden die Crate testcontainers – sie ermöglicht das programmatische Starten von Docker-Containern:
// 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\nDieses Modul kann in allen Integrationstests wiederverwendet werden. Der Container wird automatisch entfernt, wenn Tests fehlschlagen oder der Prozess beendet wird.
\n\n3. Schreiben von Integrationstests mit echten Anfragen
\n\nErstellen wir einen Test, der das Einfügen und Lesen eines Benutzers überprüft. Nehmen wir an, wir haben eine Struktur User und Funktionen create_user und get_user im Hauptcode.
// 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 // Migrationen ausführen (falls Sie sqlx verwenden)\n sqlx::migrate!().run(&pool).await.unwrap();\n \n // Benutzer erstellen\n let user = User {\n name: "Alice".into(),\n email: "alice@example.com".into(),\n };\n let created = create_user(&pool, &us