Integrationstests von Rust mit Docker: Fortgeschrittenes Niveau
Integrationstests sind ein kritisch wichtiger Schritt in der Rust-Entwicklung, insbesondere wenn Ihre Anwendung mit externen Diensten wie Datenbanken, Nachrichtenwarteschlangen oder HTTP-APIs interagiert. Die Verwendung von Docker ermöglicht die Erstellung isolierter, reproduzierbarer Umgebungen für Tests. In diesem Artikel betrachten wir fortgeschrittene Techniken: von der grundlegenden Einrichtung bis zur parallelen Ausführung von Tests mit testcontainers und docker-compose.
Warum Docker für Integrationstests?
Traditionelle Mocks und Stubs spiegeln oft nicht das reale Verhalten von Abhängigkeiten wider. Docker löst dieses Problem:
- Isolation: Jeder Test wird in einem sauberen Container gestartet.
- Reproduzierbarkeit: Gleiche Umgebung auf allen Entwicklerrechnern und in CI.
- Parallelität: Mehrere Testdatenbanken können gleichzeitig ausgeführt werden.
1. Grundlegende Einrichtung: Testen mit PostgreSQL
Beginnen wir einfach: Wir testen eine Funktion, die mit PostgreSQL arbeitet. Wir verwenden die Crate testcontainers – einen Rust-Wrapper zur Verwaltung von Docker-Containern.
Fügen Sie die Abhängigkeiten in Cargo.toml hinzu:
[dev-dependencies]testcontainers = "0.18"tokio = { version = "1", features = ["full"] }sqlx = { version = "0.8", features = ["runtime-tokio", "postgres"] }Beispiel für einen Test:
use sqlx::PgPool;use testcontainers::clients::Cli;use testcontainers::images::postgres::Postgres;
#[tokio::test]async fn test_database_connection() { let docker = Cli::default(); let postgres_instance = docker.run(Postgres::default()); let connection_string = &format!( "postgres://postgres:postgres@127.0.0.1:{}/postgres", postgres_instance.get_host_port_ipv4(5432) );
let pool = PgPool::connect(connection_string).await.unwrap();
// SQL-Abfrage ausführen let row: (i32,) = sqlx::query_as("SELECT 1 + 1") .fetch_one(&pool) .await .unwrap(); assert_eq!(row.0, 2);}Wichtig: Der Container wird nach Abschluss des Tests automatisch entfernt (dank der Drop-Implementierung).
2. Fortgeschrittene Konfiguration: Eigenes Image mit Migrationen
Oft müssen Migrationen vorab ausgeführt werden. Erstellen wir ein benutzerdefiniertes Image basierend auf einem Dockerfile:
# Dockerfile.testFROM postgres:16-alpineCOPY migrations/ /docker-entrypoint-initdb.d/Im Rust-Test verwenden wir Image:
use testcontainers::images::generic::GenericImage;
#[tokio::test]async fn test_with_migrations() { let docker = Cli::default(); let image = GenericImage::new("my-app-test:latest") .with_wait_for(testcontainers::core::WaitFor::message_on_stdout( "database system is ready to accept connections" )); let instance = docker.run(image); // ... instance verwenden}Zum Erstellen des Images in CI kann docker build -f Dockerfile.test -t my-app-test:latest . verwendet werden.
3. Parallele Ausführung von Tests mit Docker Compose
Wenn Ihr Dienst mehrere Abhängigkeiten benötigt (PostgreSQL + Redis), verwenden Sie docker-compose. Erstellen Sie eine Datei docker-compose.test.yml:
version: '3.8'services: postgres: image: postgres:16-alpine environment: POSTGRES_PASSWORD: testpass ports: - "5432" redis: image: redis:7-alpine ports: - "6379"Im Rust-Test starten wir die Compose-Umgebung:
use testcontainers::clients::Cli;use testcontainers::images::compose::Compose;
#[tokio::test]async fn test_with_compose() { let docker = Cli::default(); let compose = Compose::new("docker-compose.test.y