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

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

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

Начать курс

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

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

Зачем использовать Docker для интеграционного тестирования Swift?

Традиционное тестирование Swift-приложений часто страдает от проблем с окружением: разные версии баз данных, серверов, библиотек. Docker решает эти проблемы:

  • Изоляция: каждый тест запускается в чистом контейнере, что исключает влияние других процессов.
  • Воспроизводимость: одинаковое окружение локально, на CI-сервере и у других разработчиков.
  • Быстрое развертывание: контейнеры запускаются за секунды, а не минуты.
  • Масштабирование: легко запустить параллельные тесты с разными версиями зависимостей.

Docker особенно полезен для тестирования сетевых взаимодействий, баз данных (например, PostgreSQL или Redis) и микросервисной архитектуры.

Настройка Docker-окружения для Swift

Для начала создадим простой Swift-проект и настроим Docker. Предположим, у нас есть приложение, которое работает с базой данных PostgreSQL.

1. Создание Swift-проекта

Создайте новый проект с помощью Swift Package Manager:

mkdir SwiftIntegrationTest
cd SwiftIntegrationTest
swift package init --type executable

Добавим зависимость для работы с PostgreSQL. Откройте Package.swift и добавьте:

// swift-tools-version:5.9
import PackageDescription

let package = Package(
    name: "SwiftIntegrationTest",
    dependencies: [
        .package(url: "https://github.com/vapor/postgres-nio.git", from: "1.18.0")
    ],
    targets: [
        .executableTarget(
            name: "SwiftIntegrationTest",
            dependencies: [
                .product(name: "PostgresNIO", package: "postgres-nio")
            ]),
        .testTarget(
            name: "SwiftIntegrationTestTests",
            dependencies: ["SwiftIntegrationTest"])
    ]
)

2. Dockerfile для Swift

Создайте файл Dockerfile в корне проекта:

FROM swift:5.9-focal

WORKDIR /app

# Копируем файлы проекта
COPY Package.swift .
COPY Sources/ ./Sources/
COPY Tests/ ./Tests/

# Собираем проект (без тестов, чтобы ускорить)
RUN swift build -c release

# Команда запуска тестов
CMD ["swift", "test", "--enable-test-discovery"]

3. Docker Compose для зависимостей

Создайте docker-compose.yml для запуска тестов с PostgreSQL:

version: '3.8'

services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: testdb
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpass
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U testuser -d testdb"]
      interval: 5s
      timeout: 5s
      retries: 5

  swift-tests:
    build: .
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      DATABASE_HOST: postgres
      DATABASE_PORT: 5432
      DATABASE_NAME: testdb
      DATABASE_USER: testuser
      DATABASE_PASSWORD: testpass

Написание интеграционных тестов на Swift

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

1. Класс для работы с базой данных

Создайте файл Sources/SwiftIntegrationTest/DatabaseService.swift:

import Foundation
import PostgresNIO

actor DatabaseService {
    let connection: PostgresConnection
    
    init(host: String, port: Int, database: String, user: String, password: String) async throws {
        let config = PostgresConnection.Configuration(
            host: host,
            port: port,
            username: user,
            password: password,
            database: database,
            tls: .disable
        )
        self.connection = try await PostgresConnection.connect(configuration: config, id: 1, logger: Logger(label: "test"))
    }
    
    func createTable() async throws {
        try await connection.query("""
            CREATE TABLE IF NOT EXISTS users (
                id SERIAL PRIMARY KEY,
                name TEXT NOT NULL,
                email TEXT UNIQUE NOT NULL
            )
        """)
    }
    
    func insertUser(name: String, email: String) async throws {
        try await connection.query(
            "INSERT INTO users (name, email) VALUES (\(name), \(email))",
            logger: Logger(label: "test")
        )
    }
    
    func fetchUsers() async throws -> [(Int, String, String)] {
        let rows = try await connection.query("SELECT id, name, email FROM users", logger: Logger(label: "test"))
        return rows.map { row in
            (row[0].int!, row[1].string!, row[2].string!)
        }
    }
    
    func close() async {
        try? await connection.close()
    }
}

2. Интеграционный тест

Создайте файл Tests/SwiftIntegrationTestTests/IntegrationTests.swift:

import XCTest
@testable import SwiftIntegrationTest

final class IntegrationTests: XCTestCase {
    var dbService: DatabaseService?
    
    override func setUp() async throws {
        // Параметры подключения берутся из переменных окружения Docker
        let host = ProcessInfo.processInfo.environment["DATABASE_HOST"] ?? "localhost"
        let port = Int(ProcessInfo.processInfo.environment["DATABASE_PORT"] ?? "5432") ?? 5432
        let database = ProcessInfo.processInfo.environment["DATABASE_NAME"] ?? "testdb"
        let user = ProcessInfo.processInfo.environment["DATABASE_USER"] ?? "testuser"
        let password = ProcessInfo.processInfo.environment["DATABASE_PASSWORD"] ?? "testpass"
        
        dbService = try await DatabaseService(host: host, port: port, database: database, user: user, password: password)
        try await dbService?.createTable()
    }
    
    override func tearDown() async throws {
        await dbService?.close()
        dbService = nil
    }
    
    func testInsertAndFetchUser() async throws {
        // Вставляем пользователя
        try await dbService?.insertUser(name: "Alice", email: "alice@example.com")
        
        // Проверяем, что пользователь появился
        let users = try await dbService?.fetchUsers() ?? []
        XCTAssertEqual(users.count, 1)
        XCTAssertEqual(users[0].1, "Alice")
        XCTAssertEqual(users[0].2, "alice@example.com")
    }
    
    func testMultipleUsers() async                

Блоги

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