Интеграционное тестирование 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