تخطَّ إلى المحتوى

TestContainers دليل مرجعي سريع

TestContainers دليل مرجعي سريع

التثبيت

المتطلبات الأساسية

المتطلبالإصدارأمر التحقق
Dockerأحدث إصدار مستقرdocker --version
Java JDK٨ أو أعلىjava -version
Maven/Gradleأحدثmvn -v or gradle -v

اعتمادات Maven

<!-- Core TestContainers -->
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

<!-- JUnit 5 Integration -->
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

<!-- Database Modules -->
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mysql</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>mongodb</artifactId>
    <version>1.19.3</version>
    <scope>test</scope>
</dependency>

اعتمادات Gradle

// Kotlin DSL
testImplementation("org.testcontainers:testcontainers:1.19.3")
testImplementation("org.testcontainers:junit-jupiter:1.19.3")
testImplementation("org.testcontainers:postgresql:1.19.3")
testImplementation("org.testcontainers:mysql:1.19.3")
testImplementation("org.testcontainers:kafka:1.19.3")
// Groovy DSL
testImplementation 'org.testcontainers:testcontainers:1.19.3'
testImplementation 'org.testcontainers:junit-jupiter:1.19.3'
testImplementation 'org.testcontainers:postgresql:1.19.3'

إعداد Docker حسب المنصة

منصةأمر التثبيت
Ubuntu/Debiancurl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
macOSbrew install --cask docker
Windowsقم بتنزيل Docker Desktop من docker.com
Linux Post-Installsudo usermod -aG docker $USER && newgrp docker

الأوامر الأساسية

عمليات دورة حياة الحاويات

أمروصف
container.start()ابدأ مثيل حاوية
container.stop()توقف وأزل الحاوية
container.isRunning()تحقق مما إذا كان الحاوية قيد التشغيل حاليًا
container.getContainerId()احصل على معرف حاوية Docker
container.getDockerImageName()احصل على اسم الصورة المستخدمة
container.getLogs()استرجاع جميع سجلات الحاويات كسلسلة نصية
container.getHost()احصل على عنوان المضيف (عادةً localhost)
container.getMappedPort(int)احصل على منفذ المضيف المعين لمنفذ الحاوية
container.getExposedPorts()قم بسرد جميع منافذ الحاويات المكشوفة
container.getContainerInfo()احصل على معلومات مفصلة عن الحاويات

إنشاء الحاويات العامة

أمروصف
new GenericContainer("image:tag")أنشئ حاوية من أي صورة Docker
.withExposedPorts(port1, port2)قم بتعريض منافذ الحاوية للمضيف
.withEnv("KEY", "value")تعيين متغير البيئة
.withCommand("cmd", "arg1")تجاوز أمر الحاوية
.withLabel("key", "value")أضف تسمية Docker إلى الحاوية
.withNetworkMode("host")تعيين وضع شبكة الحاوية
.withPrivilegedMode()تشغيل الحاوية في الوضع المميز (privileged mode)
.withCreateContainerCmdModifier(cmd)قم بتعديل أمر إنشاء الحاوية
.withStartupTimeout(Duration)تعيين الحد الأقصى لوقت الانتظار عند بدء التشغيل
.withReuse(true)تمكين إعادة استخدام الحاويات عبر الاختبارات

حاويات قواعد البيانات

أمروصف
new PostgreSQLContainer("postgres:15")أنشئ حاوية PostgreSQL
new MySQLContainer("mysql:8.0")إنشاء حاوية MySQL
new MongoDBContainer("mongo:6.0")إنشاء حاوية MongoDB
.withDatabaseName("dbname")تعيين اسم قاعدة البيانات
.withUsername("user")تعيين اسم مستخدم قاعدة البيانات
.withPassword("pass")تعيين كلمة مرور قاعدة البيانات
.getJdbcUrl()احصل على عنوان URL للاتصال JDBC
.getReplicaSetUrl()احصل على عنوان URL لمجموعة النسخ المتكررة لـ MongoDB (MongoDB)
.withInitScript("init.sql")قم بتشغيل نص SQL عند بدء التشغيل
.withConfigurationOverride("path")تجاوز تكوين قاعدة البيانات

الاستخدام المتقدم

استراتيجيات الانتظار

أمروصف
.waitingFor(Wait.forListeningPort())انتظر حتى يبدأ المنفذ (port) بالاستماع
.waitingFor(Wait.forHttp("/health"))انتظر استجابة نقطة النهاية HTTP
.waitingFor(Wait.forLogMessage(".*ready.*", 1))انتظر رسالة سجل محددة
.waitingFor(Wait.forHealthcheck())انتظر حتى يمر فحص صحة Docker
.waitingFor(Wait.forSuccessfulCommand("cmd"))انتظر حتى نجاح الأمر
.forStatusCode(200)حدد رمز الحالة HTTP المتوقع
.forStatusCodeMatching(code -> code < 500)مطابق رمز الحالة المخصص
.withStartupTimeout(Duration.ofMinutes(2))تعيين مدة المهلة الزمنية للبدء
.withReadTimeout(Duration.ofSeconds(10))ضبط مهلة القراءة HTTP
.forResponsePredicate(response -> true)التحقق المخصص من الاستجابة

عمليات الملفات والمجلدات

أمروصف
.withFileSystemBind("host", "container")ربط الدليل المضيف
.withFileSystemBind(path, target, BindMode.READ_ONLY)تركيب مع وضع محدد
.withClasspathResourceMapping("res", "path")قم بتحميل مورد المسار الفرعي
.copyFileToContainer(MountableFile, "path")نسخ الملف داخل الحاوية
.withCopyFileToContainer(file, path)نسخ الملف أثناء بدء التشغيل
.withTmpFs(Map.of("/tmp", "rw"))قم بتركيب نظام الملفات tmpfs
MountableFile.forClasspathResource("file")مرجع ملف classpath
MountableFile.forHostPath("/path")مسار نظام الملفات المضيف المرجعي
BindMode.READ_WRITEالسماح بالوصول للقراءة والكتابة
BindMode.READ_ONLYالسماح بالوصول للقراءة فقط

الشبكات

أمروصف
Network.newNetwork()إنشاء شبكة Docker مخصصة
.withNetwork(network)قم بإرفاق الحاوية بالشبكة
.withNetworkAliases("alias")قم بتعيين الاسم المستعار للشبكة للحاوية
.dependsOn(otherContainer)حدد اعتمادية الحاوية
.withExtraHost("hostname", "ip")أضف إدخال إلى /etc/hosts
.getNetworkAliases()احصل على جميع الأسماء المستعارة للشبكة
.withAccessToHost(true)السماح للحاوية بالوصول إلى المضيف
Network.SHAREDاستخدم الشبكة المشتركة عبر الاختبارات
.withNetworkMode("bridge")تعيين وضع شبكة محدد
.getNetwork()احصل على الحاوية الشبكية المرتبطة بها

تنفيذ الحاويات

أمروصف
.execInContainer("cmd", "arg1")نفذ الأمر في الحاوية قيد التشغيل
.execInContainer(Charset, "cmd")نفّذ باستخدام مجموعة أحرف محددة
result.getStdout()احصل على المخرجات القياسية من التنفيذ
result.getStderr()احصل على الخطأ القياسي من التنفيذ
result.getExitCode()احصل على كود خروج الأمر
.copyFileFromContainer("path", consumer)نسخ الملف من الحاوية
.followOutput(consumer)سجلات حاويات Stream
new Slf4jLogConsumer(logger)قم بتسجيل المخرجات في مسجل SLF4J
new ToStringConsumer()التقاط المخرجات كسلسلة
new WaitingConsumer()انتظر الإخراج المحدد

تكامل JUnit 5

أمروصف
@Testcontainersتمكين امتداد TestContainers
@Containerحدد الحقل كحاوية للإدارة
static @Containerمشاركة الحاوية عبر جميع الاختبارات
@DynamicPropertySourceإدراج خصائص الحاوية
registry.add("prop", container::getUrl)أضف خاصية ديناميكية
@BeforeAllإعداد الحاويات قبل جميع الاختبارات
@AfterAllتنظيف الحاويات بعد جميع الاختبارات
@Nestedقم بتجميع الاختبارات باستخدام حاويات مشتركة
Startables.deepStart(containers).join()ابدأ حاويات متعددة بشكل متوازٍ
.withReuse(true)إعادة استخدام الحاوية عبر تشغيلات الاختبار

الإعدادات

خصائص TestContainers

إنشاء testcontainers.propertiesفي src/test/resources:

# Docker host configuration
docker.host=unix:///var/run/docker.sock
# docker.host=tcp://localhost:2375

# Container reuse (requires Docker labels support)
testcontainers.reuse.enable=true

# Image pull policy
testcontainers.image.pull.policy=DefaultPullPolicy

# Ryuk container (cleanup)
testcontainers.ryuk.disabled=false
testcontainers.ryuk.container.image=testcontainers/ryuk:0.5.1

# Checks
testcontainers.checks.disable=false

تكامل Docker Compose

@Container
static DockerComposeContainer environment = 
    new DockerComposeContainer(new File("docker-compose.yml"))
        .withExposedService("db", 5432)
        .withExposedService("redis", 6379)
        .withLocalCompose(true)
        .withPull(true)
        .waitingFor("db", Wait.forHealthcheck());

إعداد الحاويات المخصصة

public class CustomPostgreSQLContainer extends PostgreSQLContainer<CustomPostgreSQLContainer> {
    private static final String IMAGE_VERSION = "postgres:15-alpine";
    
    public CustomPostgreSQLContainer() {
        super(IMAGE_VERSION);
        this.withDatabaseName("testdb")
            .withUsername("testuser")
            .withPassword("testpass")
            .withInitScript("init.sql")
            .withCommand("postgres -c max_connections=200");
    }
}

تكامل Spring Boot

@SpringBootTest
@Testcontainers
class ApplicationTests {
    
    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
        .withDatabaseName("testdb");
    
    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
        registry.add("spring.jpa.hibernate.ddl-auto", () -> "create-drop");
    }
    
    @Test
    void contextLoads() {
        // Test with real database
    }
}

حالات الاستخدام الشائعة

حالة الاستخدام 1: اختبار التكامل PostgreSQL

Note: Since some of the texts were empty or very short, I’ve provided minimal translations that maintain the structure. For texts with no content, I’ve left them as placeholders.```java @Testcontainers @SpringBootTest class UserRepositoryTest {

@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15-alpine")
    .withDatabaseName("testdb")
    .withUsername("test")
    .withPassword("test")
    .withInitScript("schema.sql");

@DynamicPropertySource
static void registerProperties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgres::getJdbcUrl);
    registry.add("spring.datasource.username", postgres::getUsername);
    registry.add("spring.datasource.password", postgres::getPassword);
}

@Autowired
private UserRepository userRepository;

@Test
void shouldSaveAndFindUser() {
    User user = new User("john@example.com", "John Doe");
    userRepository.save(user);
    
    Optional<User> found = userRepository.findByEmail("john@example.com");
    assertTrue(found.isPresent());
    assertEquals("John Doe", found.get().getName());
}

}

```java
@Testcontainers
class KafkaIntegrationTest {
    
    @Container
    static KafkaContainer kafka = new KafkaContainer(
        DockerImageName.parse("confluentinc/cp-kafka:7.5.0")
    );
    
    @DynamicPropertySource
    static void kafkaProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.kafka.bootstrap-servers", kafka::getBootstrapServers);
    }
    
    @Autowired
    private KafkaTemplate<String, String> kafkaTemplate;
    
    @Autowired
    private MessageListener messageListener;
    
    @Test
    void shouldProcessMessage() throws Exception {
        String topic = "test-topic";
        String message = "Hello Kafka!";
        
        kafkaTemplate.send(topic, message);
        
        // Wait for message processing
        await().atMost(Duration.ofSeconds(10))
               .until(() -> messageListener.getReceivedMessages().size() == 1);
        
        assertEquals(message, messageListener.getReceivedMessages().get(0));
    }
}
```### حالة الاستخدام 3: اختبار الخدمات الدقيقة متعددة الحاويات
```java
@Testcontainers
class MicroservicesIntegrationTest {
    
    static Network network = Network.newNetwork();
    
    @Container
    static PostgreSQLContainer<?> database = new PostgreSQLContainer<>("postgres:15-alpine")
        .withNetwork(network)
        .withNetworkAliases("database")
        .withDatabaseName("orders");
    
    @Container
    static GenericContainer<?> redis = new GenericContainer<>("redis:7-alpine")
        .withNetwork(network)
        .withNetworkAliases("redis")
        .withExposedPorts(6379);
    
    @Container
    static GenericContainer<?> orderService = new GenericContainer<>("order-service:latest")
        .withNetwork(network)
        .withExposedPorts(8080)
        .withEnv("DATABASE_URL", "jdbc:postgresql://database:5432/orders")
        .withEnv("REDIS_HOST", "redis")
        .dependsOn(database, redis)
        .waitingFor(Wait.forHttp("/actuator/health").forStatusCode(200));
    
    @Test
    void shouldCreateOrder() {
        String baseUrl = "http://" + orderService.getHost() + ":" 
                       + orderService.getMappedPort(8080);
        
        // Make HTTP request to order service
        RestTemplate restTemplate = new RestTemplate();
        Order order = new Order("item-123", 2);
        
        ResponseEntity<Order> response = restTemplate.postForEntity(
            baseUrl + "/orders", order, Order.class
        );
        
        assertEquals(HttpStatus.CREATED, response.getStatusCode());
        assertNotNull(response.getBody().getId());
    }
}
```### حالة الاستخدام 4: اختبار تكامل MongoDB
```java
@Testcontainers
@DataMongoTest
class ProductRepositoryTest {
    
    @Container
    static MongoDBContainer mongodb = new MongoDBContainer("mongo:6.0")
        .withExposedPorts(27017);
    
    @DynamicPropertySource
    static void mongoProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.data.mongodb.uri", mongodb::getReplicaSetUrl);
    }
    
    @Autowired
    private ProductRepository productRepository;
    
    @Test
    void shouldFindProductsByCategory() {
        // Insert test data
        productRepository.save(new Product("Laptop", "Electronics", 999.99));
        productRepository.save(new Product("Mouse", "Electronics", 29.99));
        productRepository.save(new Product("Desk", "Furniture", 299.99));
        
        // Query by category
        List<Product> electronics = productRepository.findByCategory("Electronics");
        
        assertEquals(2, electronics.size());
        assertTrue(electronics.stream()
                  .allMatch(p -> p.getCategory().equals("Electronics")));
    }
}
```### حالة الاستخدام 5: بيئة اختبار Docker Compose
```java
@Testcontainers
class FullStackIntegrationTest {
    
    @Container
    static DockerComposeContainer<?> environment = new DockerComposeContainer<>(
        new File("src/test/resources/docker-compose-test.yml")
    )
        .withExposedService("api", 8080, 
            Wait.forHttp("/health").forStatusCode(200))
        .withExposedService("postgres", 5432, 
            Wait.forListeningPort())
        .withExposedService("redis", 6379)
        .withLocalCompose(true);
    
    @Test
    void shouldConnectToAllServices() {
        String apiHost = environment.getServiceHost("api", 8080);
        Integer apiPort = environment.getServicePort("api", 8080);
        
        String apiUrl = "http://" + apiHost + ":" + apiPort;
        
        RestTemplate restTemplate = new RestTemplate();
        ResponseEntity<String> response = restTemplate.getForEntity(
            apiUrl + "/health", String.class
        );
        
        assertEquals(HttpStatus.OK, response.getStatusCode());
    }
}
```## أفضل الممارسات

### إدارة الحاويات
- **استخدام حاويات ثابتة لفئات الاختبار**: مشاركة الحاويات عبر جميع طرق الاختبار لتقليل وقت التشغيل واستخدام الموارد
- **تنفيذ إعادة استخدام الحاويات**: تمكين`testcontainers.reuse.enable=true`لدورات اختبار التطوير المحلي الأسرع
- **تنظيف الموارد**: استخدام`@AfterAll`أو try-with-resources عند إدارة الحاويات يدويًا خارج تكامل JUnit
- **استخدام علامات صور محددة**: تجنب`latest`؛ استخدم إصدارات محددة مثل`postgres:15-alpine`للاختبارات القابلة للتكرار

### تحسين الأداء
- **بدء تشغيل الحاويات بشكل متوازٍ**: استخدم`Startables.deepStart()`لبدء تشغيل حاويات متعددة مستقلة في وقت واحد
- **تقليل إعادة تشغيل الحاويات**: استخدم`@Container`على مستوى الفئة بدلاً من مستوى الطريقة عند الإمكان
- **اختيار صور خفيفة**: فضّل الصور المعتمدة على Alpine (مثل`postgres:15-alpine`) لتقليل وقت السحب واستخدام القرص
- **تنفيذ استراتيجيات الانتظار بحكمة**: استخدم استراتيجيات انتظار مناسبة لتجنب التأخيرات غير الضرورية مع ضمان جاهزية الحاوية

### استراتيجية الاختبار
- **الاختبار مقابل التبعيات المشابهة للإنتاج**: استخدم محركات قواعد البيانات ووسطاء الرسائل الحقيقية بدلاً من النسخ المضمنة
- **عزل بيانات الاختبار**: تأكد من أن كل اختبار ينشئ وينظف بياناته الخاصة لمنع الترابط بين الاختبارات
- **استخدام أسماء الشبكة**: إنشاء شبكات مخصصة واستخدام أسماء ذات معنى للتواصل بين الحاويات
- **تنفيذ فحوصات الصحة**: تأكد دائمًا من تكوين استراتيجيات انتظار مناسبة لضمان استعداد الحاويات بالكامل قبل تنفيذ الاختبارات

### تكامل CI/CD
- **التحقق من توفر Docker**: تأكد من تثبيت Docker وإمكانية الوصول إليه في بيئة CI
- **تكوين مهل زمنية بشكل مناسب**: ضع مهلًا زمنية واقعية مع مراعاة أداء بيئة CI
- **استخدام ذاكرة التخزين المؤقت للصور**: تكوين CI لتخزين صور Docker مؤقتًا لتسريع التشغيلات اللاحقة
- **مراقبة استخدام الموارد**: كن على دراية بحدود الذاكرة ووحدة المعالجة المركزية في بيئات CI؛ اضبط تكوينات الحاويات وفقًا لذلك

### الأمان والصيانة
- **الحفاظ على تحديث TestContainers**: قم بالتحديث بانتظام إلى أحدث الإصدارات للتصحيحات الأمنية والميزات الجديدة
- **استخدام الصور الرسمية**: فضّل صور Docker الرسمية من الناشرين الموثوقين
- **تجنب الوضع المميز**: استخدم`.withPrivilegedMode()`فقط عند الضرورة القصوى
- **مراجعة المنافذ المكشوفة**: كشف المنافذ المطلوبة فقط للاختبار

## استكشاف الأخطاء وإصلاحها

| مشكلة | حل |
|-------|----------|
| **Container fails to start: "Cannot connect to Docker daemon"** | Ensure Docker is running: `docker ps`. On Linux, add user to docker group: `sudo usermod -aG docker $USER` |
| **Tests timeout during container startup** | Increase timeout: `.withStartupTimeout(Duration.ofMinutes(5))` or check Docker resources (CPU/Memory) |
| **Port binding conflicts: "Address already in use"** | Use dynamic ports: `container.getMappedPort()` instead of fixed ports, or stop conflicting services |
| **Image pull fails: "manifest unknown"** | Verify image name and tag exist on Docker Hub. Use specific tags instead of `latest` |
| **Container starts but tests fail to connect** | Check wait strategy is appropriate: add `.waitingFor(Wait.forListeningPort())` or `.waitingFor(Wait.forHealthcheck())` |
| **Out of memory errors during tests** | Reduce number of parallel containers, increase Docker memory limit in Docker Desktop settings, or use `.withReuse(true)` |
| **"Ryuk container not found" warnings** | Disable Ryuk if needed: set `testcontainers.ryuk.disabled=true` in properties (not recommended for production) |
| **Tests work locally but fail in CI** | تأكد من تثبيت Docker في CI، تحقق من إعدادات المهلة الزمنية، تحقق من اتصال الشبكة، راجع حدود موارد بيئة CI |
| **Database connection refused** | Wait for database to be ready: use `.waitingFor(Wait.forListeningPort())` and verify JDBC URL with `container.getJdbcUrl()` |
| **Container logs show errors but tests pass** | Enable log following: `container.followOutput(new Slf4jLogConsumer(log))` to debug container issues |
| **Slow test execution** | Use container reuse, share containers at class level, start containers in parallel with `Startables.deepStart()` |
| **"No such file or directory" when mounting volumes** | Use absolute paths or `MountableFile.forClasspathResource()` for classpath resources |
| **Network communication between containers fails** | Create custom network: `Network.newNetwork()` and attach containers with `.withNetwork(network)` and `.withNetworkAliases()` |
| **TestContainers properties not being read** | Ensure `testcontainers.properties` is in `src/test/resources` and properly formatted |
| **Container cleanup not happening** | Check Ryuk container is running: `docker ps | grep ryuk`. Ensure tests complete normally without hanging |
## الوحدات المتاحة

| وحدة | مجموعة Maven | وصف |
|--------|---------------|-------------|
| PostgreSQL | `testcontainers-postgresql` | حاويات قاعدة بيانات PostgreSQL |
| MySQL | `testcontainers-mysql` | حاويات قاعدة بيانات MySQL |
| MongoDB | `testcontainers-mongodb` | قاعدة بيانات MongoDB الوثائقية |
| Kafka | `testcontainers-kafka` | وسيط رسائل Apache Kafka |
| Redis | `testcontainers-redis` | متجر بيانات Redis في الذاكرة |
| Elasticsearch | `testcontainers-elasticsearch` | محرك بحث Elasticsearch |
| Cassandra | `testcontainers-cassandra` | قاعدة بيانات Cassandra اللامركزية |
| RabbitMQ | `testcontainers-rabbitmq` | وسيط الرسائل RabbitMQ |
| Selenium | `testcontainers-selenium` | حاويات أتمتة المتصفح |
| LocalStack | `testcontainers-localstack` | محاكاة خدمات AWS || Nginx