Интеграционное тестирование Swift с Docker: продвинутый уровень

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

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

Начать курс

Интеграционное тестирование Swift с Docker: продвинутый уровень

Интеграционное тестирование — ключевой этап разработки надежных Swift-приложений. В отличие от юнит-тестов, оно проверяет взаимодействие между реальными компонентами: базой данных, сетевыми сервисами, файловой системой. Docker позволяет изолировать эти зависимости, делая тесты воспроизводимыми и независимыми от окружения разработчика. В этой статье мы рассмотрим продвинутые техники интеграционного тестирования Swift-приложений (iOS и серверная сторона) с использованием Docker.

1. Основы Docker-контейнеризации для Swift-тестов

Перед тем как углубляться в продвинутые сценарии, важно понимать базовую архитектуру. Docker позволяет запускать зависимости (PostgreSQL, Redis, mock-серверы) в изолированных контейнерах, которые автоматически поднимаются перед тестами и уничтожаются после.

Для серверного Swift (Vapor, Kitura) мы используем официальный образ Swift. Для iOS-приложений Docker не может запустить симулятор, но отлично подходит для тестирования сетевого слоя и бэкенда.

Пример базового Docker Compose файла для Vapor проекта:

version: '3.8'
services:
  app:
    image: swift:5.10-focal
    working_dir: /app
    volumes:
      - .:/app
    depends_on:
      - db
      - redis
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/testdb
      - REDIS_URL=redis://redis:6379

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: testdb
    ports:
      - "5432:5432"

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

Этот файл поднимает три контейнера: сам Swift-сервер, PostgreSQL и Redis. Теперь напишем интеграционный тест, который проверяет сохранение пользователя в реальной базе данных.

2. Написание интеграционных тестов с XCTest и Docker

Для интеграционного тестирования серверного Swift мы будем использовать стандартный фреймворк XCTest. Ключевой момент — тесты должны запускаться внутри контейнера, который имеет доступ к другим контейнерам.

Пример теста для Vapor приложения:

import XCTest
@testable import App
import Vapor

final class UserIntegrationTests: XCTestCase {

    var app: Application!

    override func setUp() async throws {
        app = try await Application.make(.testing)
        try await configure(app)

        // Миграция базы данных перед каждым тестом
        try await app.autoMigrate()
    }

    override func tearDown() async throws {
        try await app.autoRevert()
        try await app.asyncShutdown()
    }

    func testCreateAndFetchUser() async throws {
        // Создаем пользователя через API
        let userDTO = UserDTO(name: "Alice", email: "alice@test.com")
        try await app.test(.POST, "/users", beforeRequest: { req in
            try req.content.encode(userDTO)
        }, afterResponse: { res in
            XCTAssertEqual(res.status, .created)
            let createdUser = try res.content.decode(User.self)
            XCTAssertEqual(createdUser.name, "Alice")
        })

        // Проверяем, что пользователь сохранился в базе
        try await app.test(.GET, "/users/\(aliceID)", afterResponse: { res in
            XCTAssertEqual(res.status, .ok)
            let fetchedUser = try res.content.decode(User.self)
            XCTAssertEqual(fetchedUser.email, "alice@test.com")
        })
    }
}

Этот тест запускает приложение, выполняет HTTP-запросы к реальному серверу внутри контейнера и проверяет ответы. Docker гарантирует, что база данных и Redis чисты для каждого запуска.

3. Mock-серверы и изоляция внешних зависимостей

На продвинутом уровне часто требуется тестировать взаимодействие с внешними API (платежные системы, погодные сервисы). Вместо того чтобы делать реальные запросы, мы поднимаем mock-сервер в отдельном Docker-контейнере.

Используем WireMock для имитации внешнего API:

// Docker Compose добавляем сервис:
  wiremock:
    image: wiremock/wiremock:latest
    ports:
      - "8080:8080"
    volumes:
      - ./wiremock/stubs:/home/wiremock
    command: --global-response-templating

Тест, использующий mock:

func testPaymentService() async throws {
    // Настраиваем WireMock на ответ
    let stubMapping = """
    {
        "request": { "method": "POST", "url": "/payments" },
        "response": {
            "status": 200,
            "jsonBody": { "status": "success", "transactionId": "txn_123" }
        }
    }
    """
    let url = URL(string: "http://wiremock:8080/__admin/mappings")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = stubMapping.data(using: .utf8)
    request.setValue("application/json", forHTTPHeaderField: "Content-Type")
    _ = try await URLSession.shared.data(for: request)

    // Теперь тестируем наш сервис
    let paymentService = PaymentService(baseURL: "http://wiremock:8080")
    let result = try await paymentService.processPayment(amount: 100)
    XCTAssertEqual(result.status, "success")
    XCTAssertEqual(result.transactionId, "txn_123")
}

Такой подход позволяет тестировать все сценарии: успешные ответы, ошибки, таймауты, не подключаясь к реальным сервисам.

4. Параллельное выполнение тестов и CI/CD интеграция

Одна из главных проблем интеграционных тестов — время выполнения. Docker позволяет легко распараллелить тесты, поднимая несколько изолированных наборов контейнеров.

Стратегия параллельного выполнения:

  • Sharding тестов: разделите тесты на группы (по модулям или фичам) и запускайте каждую группу в отдельном Docker Compose проекте
  • Использование тегов: маркируйте тесты, которые требуют одной и той же базы данных, чтобы избежать конфликтов
  • Docker Compose с профилями: используйте профили для разных наборов зависимостей

Пример Makefile для параллельного запуска:

# Makefile
.PHONY: test-parallel

test-parallel:
    @echo "Запуск интеграционных тестов в 4 параллельных потока..."
    docker compose up -d db redis wiremock
    sleep 5  # Ждем, пока контейнеры будут готовы
    swift test --parallel --num-workers                

Блоги

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