Разработка Unit-тестов для iOS-приложения (XCTest)

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

Это лишь некоторые из типы мобильных приложений, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента.

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Разработка Unit-тестов для iOS-приложения (XCTest)
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Разработка Unit-тестов для iOS-приложения (XCTest)

Типичная ситуация: ViewModel разрастается до 500 строк, в ней смешана бизнес-логика, форматирование данных и прямые вызовы сети. Добавляешь новую фичу — ломается что-то в старом потоке. Откатываешь — снова работает, но причина неясна. Юнит-тесты на XCTest — это не про «покрытие ради покрытия», а про возможность рефакторить без страха.

Что тестируем и как

Бизнес-логика — главный приоритет. ViewModel, Interactor, UseCase — всё, где есть ветвления if/switch, вычисления, трансформации данных. Протокол-ориентированный подход Swift делает это удобным: зависимости инжектируются через протоколы, в тестах подменяются mock-объектами.

// Протокол сервиса
protocol UserServiceProtocol {
    func fetchUser(id: String) async throws -> User
}

// Mock для тестов
class MockUserService: UserServiceProtocol {
    var stubbedUser: User?
    var stubbedError: Error?

    func fetchUser(id: String) async throws -> User {
        if let error = stubbedError { throw error }
        return stubbedUser!
    }
}

// Тест
func testFetchUserSuccess() async throws {
    let mockService = MockUserService()
    mockService.stubbedUser = User(id: "1", name: "Test")
    let sut = UserViewModel(service: mockService)

    await sut.loadUser(id: "1")

    XCTAssertEqual(sut.user?.name, "Test")
    XCTAssertFalse(sut.isLoading)
}

Асинхронный код — второй по важности блок. Современный Swift с async/await тестируется нативно: XCTest поддерживает async тест-функции с iOS 15+ и Xcode 13+. Для Combine-based кода — XCTestExpectation + sink.

Граничные случаи — то, что реально падает в продакшене: пустой массив, nil-значение, строка с юникодом, дата в другом timezone. Не «happy path», а именно edge cases.

Архитектура, удобная для тестирования

XCTest-тесты пишутся просто, когда архитектура предполагает инверсию зависимостей. MVVM с DI через инициализатор, Clean Architecture с UseCase — тестируются прямо. Singleton-ы и статические методы — нет. Если проект не использует DI, часть работы — рефакторинг перед написанием тестов.

Частая проблема: ViewModel обращается к UserDefaults напрямую, к Date() напрямую. Оба нужно обернуть в протоколы и инжектировать — иначе тесты будут зависеть от системного состояния и времени запуска.

Тестирование Combine и async/await

// Combine: тестируем Publisher
func testPublisherEmitsValue() {
    let expectation = expectation(description: "Value received")
    var cancellables = Set<AnyCancellable>()

    sut.statePublisher
        .dropFirst()  // пропускаем начальное состояние
        .sink { state in
            XCTAssertEqual(state, .loaded)
            expectation.fulfill()
        }
        .store(in: &cancellables)

    sut.loadData()
    waitForExpectations(timeout: 2)
}

CI-интеграция

Тесты запускаем на каждый PR через xcodebuild test -scheme MyApp -destination 'platform=iOS Simulator,name=iPhone 15'. Матрица версий iOS фиксируется в GitHub Actions / Bitrise конфигурации. Code coverage репортируем через Xcode Coverage Report или xcov gem. Цель покрытия — не 100%, а логики: ViewModel/Interactor/UseCase должны быть покрыты на 80%+, UI — не трогаем юнит-тестами.

Типичные ошибки в iOS unit-тестах

  • @testable import без флага -enable-testing в build settings — импорт не работает в CI
  • Тесты, зависящие от порядкаXCTest не гарантирует порядок выполнения, каждый тест должен быть изолирован через setUp()/tearDown()
  • Реальные сетевые запросы в тестах — тест становится нестабильным и медленным. Всегда мокируем через URLProtocol subclass или URLSession с кастомным URLSessionConfiguration

Процесс работы

Аудит существующего кода → выделение тестируемых компонентов → при необходимости минимальный рефакторинг для DI → написание тестов → интеграция в CI. Отчёт по покрытию после завершения.

Срок: 3–5 дней в зависимости от объёма кодовой базы и текущего уровня архитектурной изолированности компонентов.