Интеграционное тестирование Rust с Docker: полное руководство

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Интеграционное тестирование 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.rs
use 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("Failed to connect to Postgres");
    
    sqlx::query("CREATE TABLE IF NOT EXISTS users (id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL)")
        .execute(&pool)
        .await
        .expect("Migration failed");
    
    (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::time::sleep(std::time::Duration::from_millis(100)).await;
    
    // Отправляем запрос
    let client = reqwest::Client::new();
    let response = client
        .post(format!("http://127.0.0.1:{}/users", port))
        .json(&serde_json::json!({"name": "Alice"}))
        .send()
        .await
        .unwrap();
    
    assert_eq!(response.status(), 201);
    
    let body: serde_json::Value = response.json().await.unwrap();
    assert_eq!(body["name"], "Alice");
}

Шаг 2: Использование Docker Compose для сложных сценариев

Если ваш проект использует несколько сервисов, удобнее применять docker-compose.yml:

// tests/docker-compose.yml
version: '3.8'
services:
  postgres:
    image: postgres:15-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: test_db
    ports:
      - "5432:5432"
  
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Для запуска тестов с Docker Compose используйте следующий подход:

// tests/common/mod.rs
use std::process::Command;

pub fn start_docker_compose() {
    Command::new("docker-compose")
        .args(["-f", "tests/docker-compose.yml", "up", "-d"])
        .status()
        .expect("Failed to start Docker Compose");
    
    // Ждем готовности сервисов
    std::thread::sleep(std::time::Duration::from_secs(5));
}

pub fn stop_docker_compose() {
    Command::new("docker-compose")
        .args(["-f", "tests/docker-compose.yml", "down"])
        .status()
        .expect("Failed to stop Docker Compose");
}

Шаг 3: Интеграция с CI/CD (GitHub Actions)

Чтобы тесты работали в CI, добавьте Docker в ваш workflow:

# .github/workflows/tests.yml
name: Integration Tests
on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install Rust
        uses: actions-rs/toolchain@v1
        with:
          toolchain: stable
      
      - name: Install Docker
        uses: docker/setup-buildx-action@v2
      
      - name: Run integration tests
        run: cargo test --test integration_tests -- --ignored
        env:
          DOCKER_HOST: unix:///var/run/docker.sock

                
                

Блоги

Рекомендаций по книгам