Тестирование в Swift: unit, integration, e2e от А до Я

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

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

Начать курс

Введение: почему тестирование — основа надежного Swift-приложения

Разработка под iOS и macOS требует высокой надежности кода. Ошибка в логике работы приложения может стоить пользовательского опыта, а в коммерческих проектах — денег. Тестирование в Swift — это не просто «хороший тон», а обязательный этап разработки. Оно позволяет выявить баги на ранних стадиях, упростить рефакторинг и повысить качество кода.

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

1. Unit-тестирование в Swift: основа основ

Unit-тесты проверяют отдельные модули (функции, методы, классы) в изоляции. Их главная цель — убедиться, что каждая «единица» кода работает корректно.

Настройка XCTest

XCTest встроен в Xcode. Для создания тестового таргета:

  • Откройте проект в Xcode.
  • Выберите File → New → Target.
  • Выберите Unit Testing Bundle.

После создания вы получите файл вида YourAppTests.swift.

Пример unit-теста

Допустим, у нас есть функция расчета скидки:

func calculateDiscount(price: Double, discount: Double) -> Double {
    guard discount >= 0 && discount <= 100 else { return price }
    return price * (1 - discount / 100)
}

Пишем тест:

import XCTest
@testable import YourApp

class DiscountTests: XCTestCase {
    func testCalculateDiscount_ValidInput() {
        let result = calculateDiscount(price: 1000, discount: 20)
        XCTAssertEqual(result, 800, accuracy: 0.01)
    }
    
    func testCalculateDiscount_InvalidDiscount() {
        let result = calculateDiscount(price: 500, discount: 150)
        XCTAssertEqual(result, 500)
    }
}

Best practices для unit-тестов:

  • Один тест — одна проверка.
  • Используйте setUp() для инициализации общих объектов.
  • Избегайте зависимостей от сети, БД или UI — используйте моки.

2. Integration-тесты: проверка взаимодействия компонентов

Integration-тесты проверяют, как разные модули работают вместе. Например, взаимодействие модели данных с сетевым слоем или базой данных CoreData.

Пример: тестирование сетевого запроса

Создадим простой сервис:

class NetworkService {
    func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {
        // Реальный запрос к API
    }
}

Интеграционный тест может проверить, что сервис корректно обрабатывает ответ от реального сервера (или от локального mock-сервера):

class NetworkServiceIntegrationTests: XCTestCase {
    var service: NetworkService!
    
    override func setUp() {
        super.setUp()
        service = NetworkService()
    }
    
    func testFetchUser_ReturnsUser() {
        let expectation = self.expectation(description: "Fetch user")
        
        service.fetchUser { result in
            switch result {
            case .success(let user):
                XCTAssertNotNil(user.name)
            case .failure:
                XCTFail("Request failed")
            }
            expectation.fulfill()
        }
        
        waitForExpectations(timeout: 5)
    }
}

Важно: Интеграционные тесты медленнее unit-тестов, поэтому их запускают реже (например, перед мерджем в main).

3. E2E-тестирование (сквозное) с XCUITest

E2E-тесты (End-to-End) имитируют действия пользователя: нажатие кнопок, ввод текста, проверка отображения UI. В Swift для этого используется фреймворк XCUITest.

Настройка UI-тестов

Создайте UI Testing Bundle так же, как и unit-тесты. Xcode автоматически сгенерирует файл с суффиксом UITests.

Пример e2e-теста

Предположим, у нас есть экран логина:

class LoginUITests: XCTestCase {
    var app: XCUIApplication!
    
    override func setUp() {
        super.setUp()
        continueAfterFailure = false
        app = XCUIApplication()
        app.launch()
    }
    
    func testSuccessfulLogin() {
        let usernameField = app.textFields["username"]
        let passwordField = app.secureTextFields["password"]
        let loginButton = app.buttons["login"]
        
        usernameField.tap()
        usernameField.typeText("testuser")
        
        passwordField.tap()
        passwordField.typeText("password123")
        
        loginButton.tap()
        
        let welcomeLabel = app.staticTexts["Welcome, testuser!"]
        XCTAssertTrue(welcomeLabel.waitForExistence(timeout: 5))
    }
}

Советы по e2e-тестам:

  • Используйте accessibilityIdentifier для надежного поиска элементов.
  • Не тестируйте сторонние API — используйте моки или локальные серверы.
  • Запускайте e2e-тесты на реальных устройствах или симуляторах с разными версиями iOS.

Заключение: пирамида тестирования в Swift

Эффективная стратегия тестирования строится по принципу пирамиды тестирования:

  • Unit-тесты — основа (большая часть, быстрые и дешевые).
  • Integration-тесты — средний слой (проверка связок).
  • E2E-тесты — вершина (мало, но критически важны для UI).

В Swift эта модель реализуется через XCTest и XCUITest. Начните с unit-тестов для бизнес-логики, затем добавьте интеграционные тесты для сетевых или базовых операций, и в конце — e2e для ключевых пользовательских сценариев.

Регулярное тестирование не только повышает качество приложения, но и ускоряет разработку за счет раннего обнаружения ошибок. Используйте XCT

Блоги

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