اختبار التكامل في Rust باستخدام Docker: المستوى المتقدم

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة في اختبار التكامل لـ Rust باستخدام Docker

\n

اختبار التكامل هو مرحلة أساسية في تطوير تطبيقات الخادم الموثوقة بلغة Rust. على عكس اختبارات الوحدة، تختبر اختبارات التكامل تفاعل المكونات مع التبعيات الحقيقية: قواعد البيانات، التخزين المؤقت، واجهات API الخارجية. يتيح استخدام Docker عزل بيئة الاختبار وضمان قابلية التكرار. في هذه المقالة، سنستعرض تقنيات متقدمة: التشغيل المتوازي للحاويات، إدارة الحالة، خدمات المحاكاة (mock-services)، والعمل مع البرمجة غير المتزامنة عبر Tokio.

\n\n

إعداد بيئة الاختبار: Docker و testcontainers

\n

لاختبارات التكامل في Rust، من المناسب استخدام الكريت testcontainers. يسمح بتشغيل حاويات Docker برمجياً، وانتظار جاهزيتها، وتنظيف الموارد بشكل صحيح. لنبدأ بالإعداد الأساسي.

\n

تثبيت التبعيات

\n

أضف إلى Cargo.toml:

\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

مثال أساسي: حاوية PostgreSQL

\n

لننشئ اختباراً يقوم بتشغيل PostgreSQL، وينفذ الترحيل (migration)، ويتحقق من إدراج البيانات:

\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("فشل الاتصال");\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)", &[&"أليس"])\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), "أليس");\n}
\n

سيتم حذف الحاوية تلقائياً بعد انتهاء الاختبار بفضل المدمر (destructor) الخاص بـ Container. هذا يلغي الحاجة إلى التنظيف اليدوي.

\n\n

التشغيل المتوازي للاختبارات وإدارة الموارد

\n

بشكل افتراضي، ينفذ Rust الاختبارات في خيوط متوازية. إذا قام كل اختبار بتشغيل حاويته الخاصة، فقد يؤدي ذلك إلى تعارض في المنافذ وزيادة تحميل Docker. سننظر في نهجين.

\n

استخدام منافذ مختلفة عبر التخصيص الديناميكي

\n

يقوم كريت testcontainers تلقائياً بتعيين منافذ حرة على المضيف. هذا يحل مشكلة التصادمات، لكنه يزيد من استهلاك الموارد. للحاويات الثقيلة (مثل قواعد البيانات)، من الأفضل استخدام مثيل مشترك:

\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    // ... الاتصال كما في المثال السابق\n    (client, container)\n}
\n

ومع ذلك، هذا النهج خطير: قد تؤثر الاختبارات على بعضها البعض من خلال الحالة المشتركة. يُوصى باستخدام قواعد بيانات منفصلة داخل حاوية واحدة أو إعادة تعيين الحالة

المدونات

توصيات الكتب