Integrationstests von Rust mit Docker: Fortgeschrittenes Niveau

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einführung in Integrationstests von Rust mit Docker

\n

Integrationstests sind ein entscheidender Schritt bei der Entwicklung zuverlässiger Serveranwendungen in Rust. Im Gegensatz zu Unit-Tests überprüfen Integrationstests die Interaktion von Komponenten mit realen Abhängigkeiten: Datenbanken, Caches, externen APIs. Die Verwendung von Docker ermöglicht es, die Testumgebung zu isolieren und Reproduzierbarkeit zu gewährleisten. In diesem Artikel behandeln wir fortgeschrittene Techniken: paralleles Starten von Containern, Zustandsverwaltung, Mock-Dienste und die Arbeit mit Asynchronität über Tokio.

\n\n

Einrichtung der Testumgebung: Docker und testcontainers

\n

Für Integrationstests in Rust ist die Crate testcontainers praktisch. Sie ermöglicht das programmatische Starten von Docker-Containern, das Warten auf deren Bereitschaft und die korrekte Bereinigung von Ressourcen. Beginnen wir mit der grundlegenden Einrichtung.

\n

Abhängigkeiten installieren

\n

Fügen Sie in Cargo.toml hinzu:

\n
[dev-dependencies]\ntestcontainers = "0.21"\ntokio = { version = "1", features = ["full"] }\nreqwest = { version = "0.12", features = ["json"] }\nserde = { version = "1", features = ["derive"] }\nserde_json = "1"
\n

Basisbeispiel: Container mit PostgreSQL

\n

Erstellen wir einen Test, der PostgreSQL startet, eine Migration durchführt und das Einfügen von Daten überprüft:

\n
use testcontainers::{clients, images::postgres::Postgres, Container};\nuse tokio_postgres::{NoTls, Client};\n\nasync fn setup_db() -> (Client, Container<'static, Postgres>) {\n    let docker = clients::Cli::default();\n    let pg_image = Postgres::default();\n    let container = docker.run(pg_image);\n    let host = container.get_host();\n    let port = container.get_host_port_ipv4(5432);\n    let connection_string = format!(\n        "host={host} port={port} user=postgres dbname=postgres",\n    );\n    let (client, connection) = tokio_postgres::connect(&connection_string, NoTls)\n        .await\n        .expect("Failed to connect");\n    tokio::spawn(async move {\n        if let Err(e) = connection.await {\n            eprintln!("Connection error: {e}");\n        }\n    });\n    (client, container)\n}\n\n#[tokio::test]\nasync fn test_insert_user() {\n    let (client, _container) = setup_db().await;\n    client\n        .simple_query("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name TEXT)")\n        .await\n        .unwrap();\n    client\n        .execute("INSERT INTO users (name) VALUES ($1)", &[&"Alice"])\n        .await\n        .unwrap();\n    let rows = client.query("SELECT name FROM users", &[]).await.unwrap();\n    assert_eq!(rows.len(), 1);\n    assert_eq!(rows[0].get::<_, String>(0), "Alice");\n}
\n

Der Container wird nach Abschluss des Tests automatisch gelöscht, dank des Destruktors von Container. Dies erspart die manuelle Bereinigung.

\n\n

Paralleles Ausführen von Tests und Ressourcenverwaltung

\n

Standardmäßig führt Rust Tests in parallelen Threads aus. Wenn jeder Test seinen eigenen Container startet, kann dies zu Portkonflikten und einer Überlastung von Docker führen. Betrachten wir zwei Ansätze.

\n

Verwendung verschiedener Ports durch dynamische Zuweisung

\n

Die Crate testcontainers weist automatisch freie Ports auf dem Host zu. Dies löst das Problem von Kollisionen, erhöht jedoch den Ressourcenverbrauch. Für schwere Container (z. B. Datenbanken) ist es besser, eine gemeinsame Instanz zu verwenden:

\n
use once_cell::sync::Lazy;\nuse testcontainers::clients;\n\nstatic DOCKER: Lazy<clients::Cli> = Lazy::new(|| clients::Cli::default());\n\nasync fn get_shared_postgres() -> (tokio_postgres::Client, Container<'static, Postgres>) {\n    let pg_image = Postgres::default();\n    let container = DOCKER.run(pg_image);\n    // ... Verbindung wie im vorherigen Beispiel\n    (client, container)\n}
\n

Dieser Ansatz ist jedoch riskant: Tests können sich gegenseitig über den gemeinsamen Zustand beeinflussen. Es wird empfohlen, separate Datenbanken innerhalb eines Containers zu verwenden oder den Zustand zurückzusetzen.

Blogs

Buchempfehlungen