اختبار التكامل لـ Rust مع Docker: من النظرية إلى التطبيق
التطوير الحديث في Rust لا يتطلب فقط التحقق من الوظائف الفردية (اختبارات الوحدة)، بل يتطلب أيضًا اختبار تفاعل المكونات مع الخدمات الخارجية — قواعد البيانات، التخزين المؤقت، واجهات API. اختبار التكامل مع Docker يسمح بعزل البيئة، وضمان قابلية التكرار، وتجنب مشاكل "يعمل على جهازي". في هذه المقالة، سنشرح كيفية إعداد اختبارات التكامل لتطبيقات Rust باستخدام Docker.
لماذا Docker لاختبار التكامل في Rust؟
يحل Docker ثلاث مشاكل رئيسية:
- عزل البيئة — يتم تشغيل الاختبارات في حاوية نظيفة، دون تأثير الإعدادات المحلية
- قابلية التكرار — نفس الظروف على جميع أجهزة المطورين وفي CI/CD
- إدارة التبعيات — التشغيل التلقائي لـ PostgreSQL، Redis، Kafka وغيرها من الخدمات
إعداد المشروع: الهيكل والتبعيات
سننشئ مشروعًا مع اختبارات التكامل لخادم ويب يعمل بـ Actix-web ويتعامل مع PostgreSQL.
# Cargo.toml[package]name = "rust-docker-testing"version = "0.1.0"edition = "2021"
[dependencies]actix-web = "4"serde = { version = "1", features = ["derive"] }tokio = { version = "1", features = ["full"] }sqlx = { version = "0.7", features = ["runtime-tokio", "postgres"] }dotenv = "0.15"
[dev-dependencies]testcontainers = "0.14"reqwest = { version = "0.11", features = ["json"] }لاحظ testcontainers — هذه مكتبة Rust لإدارة حاويات Docker مباشرة من الاختبارات.
هيكل المشروع
الهيكل الموصى به لاختبارات التكامل:
.├── src/│ ├── main.rs│ ├── lib.rs│ ├── models.rs│ └── handlers.rs├── tests/│ ├── common/│ │ └── mod.rs # أدوات مساعدة مشتركة للاختبارات│ ├── integration_tests.rs # اختبارات التكامل الرئيسية│ └── docker-compose.yml # للتشغيل اليدوي├── Dockerfile└── .envالخطوة 1: كتابة اختبار تكامل مع Docker
سننشئ اختبارًا يشغل PostgreSQL في Docker، ويقوم بتشغيل الترحيلات، ويتحقق من API.
// tests/integration_tests.rsuse testcontainers::clients;use testcontainers::images::postgres::Postgres;use sqlx::postgres::PgPoolOptions;use std::net::TcpListener;
async fn setup_test_db() -> (String, testcontainers::Container) { let docker = clients::Cli::default(); let postgres_image = Postgres::default(); let container = docker.run(postgres_image); let host = container.get_host().unwrap(); let port = container.get_host_port_ipv4(5432).unwrap(); let database_url = format!("postgres://postgres:postgres@{}:{}/postgres", host, port); // تنفيذ الترحيلات let pool = PgPoolOptions::new() .max_connections(5) .connect(&database_url) .await .expect("فشل الاتصال بـ Postgres"); sqlx::query("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL)") .execute(&pool) .await .expect("فشل الترحيل"); (database_url, container)}
#[tokio::test]async fn test_create_user() { let (db_url, _container) = setup_test_db().await; // تشغيل التطبيق على منفذ عشوائي let listener = TcpListener::bind("127.0.0.1:0").unwrap(); let port = listener.local_addr().unwrap().port(); let server = actix_web::HttpServer::new(move || { actix_web::App::new() .app_data(actix_web::web::Data::new(db_url.clone())) .route("/users", actix_web::web::post().to(handlers::create_user)) }) .listen(listener) .unwrap() .run(); tokio::spawn(server); // انتظار تشغيل الخادم tokio::ti
```