Rust और Docker के साथ इंटीग्रेशन टेस्टिंग का परिचय
\nइंटीग्रेशन टेस्टिंग Rust में विश्वसनीय सर्वर एप्लिकेशन बनाने का एक महत्वपूर्ण चरण है। यूनिट टेस्ट के विपरीत, इंटीग्रेशन टेस्ट वास्तविक डिपेंडेंसीज जैसे डेटाबेस, कैश और बाहरी API के साथ कंपोनेंट्स के इंटरैक्शन की जांच करते हैं। Docker का उपयोग टेस्ट एनवायरनमेंट को आइसोलेट करता है और रिप्रोड्यूसिबिलिटी सुनिश्चित करता है। इस लेख में हम एडवांस्ड तकनीकों को कवर करेंगे: पैरेलल कंटेनर रन, स्टेट मैनेजमेंट, मॉक सर्विसेज और Tokio के साथ एसिंक्रोनस वर्कफ्लो।
\n\nटेस्ट एनवायरनमेंट सेटअप: Docker और testcontainers
\nRust में इंटीग्रेशन टेस्ट के लिए testcontainers क्रेट बहुत उपयोगी है। यह प्रोग्रामेटिक रूप से Docker कंटेनर चलाने, उनके तैयार होने की प्रतीक्षा करने और संसाधनों को साफ करने की सुविधा देता है। आइए बेसिक सेटअप से शुरू करें।
डिपेंडेंसीज इंस्टॉल करना
\nCargo.toml में निम्नलिखित जोड़ें:
[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बेसिक उदाहरण: PostgreSQL कंटेनर
\nएक टेस्ट बनाते हैं जो PostgreSQL चलाता है, माइग्रेशन करता है और डेटा इंसर्शन की जांच करता है:
\nuse 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("कनेक्ट करने में विफल");\n tokio::spawn(async move {\n if let Err(e) = connection.await {\n eprintln!("कनेक्शन एरर: {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टेस्ट खत्म होने पर Container का डिस्ट्रक्टर अपने आप कंटेनर को हटा देता है। इससे मैन्युअल क्लीनअप की जरूरत नहीं रहती।
पैरेलल टेस्ट रन और रिसोर्स मैनेजमेंट
\nडिफ़ॉल्ट रूप से Rust टेस्ट को पैरेलल थ्रेड्स में चलाता है। अगर हर टेस्ट अपना कंटेनर बनाता है, तो पोर्ट कॉन्फ्लिक्ट और Docker ओवरलोड हो सकता है। यहाँ दो समाधान हैं।
\nडायनामिक पोर्ट अलॉटमेंट का उपयोग
\ntestcontainers क्रेट अपने आप होस्ट पर फ्री पोर्ट असाइन करता है। यह कॉलिजन की समस्या हल करता है, लेकिन रिसोर्स खपत बढ़ाता है। भारी कंटेनरों (जैसे डेटाबेस) के लिए शेयर्ड इंस्टेंस बेहतर है:
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 // ... पिछले उदाहरण की तरह कनेक्शन\n (client, container)\n}\nलेकिन यह तरीका खतरनाक हो सकता है: टेस्ट एक-दूसरे के डेटा को प्रभावित कर सकते हैं। एक ही कंटेनर में अलग-अलग डेटाबेस का उपयोग करें या टेस्ट के बाद स्टेट रीसेट करें।