Proxyman и Har для предсказуемых ios Ui‑тестов: перехват трафика и автоматические моки

Proxyman и HAR как основа предсказуемых UI-тестов в iOS

Меня зовут Ринат, я занимаюсь развитием нескольких iOS‑продуктов в Naumen — в том числе клиента для SMP‑сервера и SDK чата. Делюсь практикой, как мы стабилизировали интеграционные UI‑тесты: перехватываем сетевой трафик приложения, экспортируем его в HAR и автоматически превращаем в моки. В результате тесты больше не зависят от капризов стенда и сети, запускаются предсказуемо и проверяют именно то, что нам нужно.

Почему iOS UI‑тесты часто «шатаются»

UI‑тесты клиент‑серверных приложений завязаны на состоянии бэкенда и его доступности. Достаточно, чтобы:
- кто‑то сменил пароль тестового пользователя,
- на стенде обновилась база,
- недоступен Wi‑Fi,
- упал сервер,
— и сценарии, которые вчера проходили, сегодня рушатся. Разработчик контролирует клиент, но не контролирует контуры и данные сервера. Ручные проверки занимают время, а автоматические становятся источником ложных падений.

Два пути: свой сервер на тест или имитация сети

1) Поднимать отдельный сервер под каждый UI‑тест с заранее подготовленным наполнением.
- Плюсы: тесты чуть меньше зависят от общего стенда.
- Минусы: долго поднимается окружение, оно ломается от обновлений бэкенда, сложно моделировать интерактивные сценарии (например, ответ оператора в чате), а часто просто нет «тестовых» API‑ручек для принудительного перевода сервера в нужное состояние.

2) Мокировать сеть — имитировать взаимодействие клиента и сервера на уровне HTTP/WebSocket.
- Плюсы: для каждого теста можно приготовить свои ответы; легко моделировать ошибки, нестандартные статусы, задержки; можно проверять разные версии API, даже если сервер уже удалил старые; писать тесты под согласованную, но ещё не выпущенную версию API; запускать тесты офлайн и в параллели.
- Важное уточнение: отсутствие реального сервера некритично, если цель — проверить корректность работы именно iOS‑клиента и соблюдение контракта API. Мы валидируем поведение по спецификации, а не инфраструктуру.

Как устроены UI‑тесты в Xcode

UI‑тесты запускают приложение через XCUIApplication, управляют его жизненным циклом, передают launchArguments и launchEnvironment, синхронизируются по условиям интерфейса. Ключ к детерминизму — контролируемые начальные условия: данные и ответы сервера должны быть предсказуемыми.

Почему мокируем сеть, а не слой данных

- Мок слоя данных тестирует только внутреннюю реализацию. Чуть изменится архитектура — тесты посыпятся, хотя поведение UI останется верным.
- Мок сети проверяет контракт «клиент ↔ API». Мы валидируем обработку реальных эндпоинтов, коды статусов, заголовки, форматы JSON, даже если под капотом появляется новый слой кэширования или меняется способ парсинга.

Инструменты: Proxyman, HAR и кастомный URLProtocol

Зачем Proxyman. Это удобный прокси‑перехватчик: направляем трафик приложения через него, фиксируем все запросы/ответы, инспектируем заголовки и тела. Главное — экспорт в формат HAR.

Что такое HAR. HTTP Archive — стандартное JSON‑представление сессии: запросы, ответы, заголовки, тела, тайминги. Формат универсален, легко парсится и пригоден для автоматической генерации моков. Мы храним его в репозитории рядом с тестами, версионируем и обновляем вместе с изменениями API.

Как мы готовим моки из реального трафика

- Перехватываем трафик приложения через Proxyman.
- Фильтруем только нужные домены и сценарии.
- Экспортируем в HAR.
- Консольной утилитой преобразуем HAR в набор моков: на выходе файлы с ответами и индекс сопоставления по методам, путям, query‑параметрам и значимым заголовкам. Это экономит часы ручной работы и исключает человеческие ошибки.

Подмена сетевого слоя через URLProtocol

- Реализуем собственный класс, наследуемый от URLProtocol.
- Регистрируем его для тестовой сборки, чтобы он перехватывал все запросы URLSession.
- По входящему URL, HTTP‑методу, заголовкам и телу ищем совпадение в индексе моков, возвращаем заранее сохранённый ответ, статус‑код, заголовки, тело, а при необходимости — симулируем задержки и ошибки.
- Для бинарных ответов (изображения, файлы) используем base64 из HAR или сохраняем как ресурсы.

Управление сценариями через launchArguments

- Перед запуском теста передаём аргументы в XCUIApplication: идентификатор сценария, включение/выключение задержек, флаги ошибок.
- URLProtocol читает аргументы, выбирает соответствующий набор моков (например, «chat_happy_path» или «order_status_error_500») и корректно маршрутизирует ответы.
- Это позволяет одной и той же тестовой логике проверять разные ветки поведения без изменения кода теста.

Имитация WebSocket‑событий через CFNotificationCenter

- Сокеты сложнее HTTP, так как сервер пушит события. Мы отделяем «транспорт» от «событий»: в тестовой сборке слой сокета подписывается на локальные уведомления.
- Тест или вспомогательный драйвер публикует события через CFNotificationCenter с полезной нагрузкой (JSON), а приложение воспринимает их как пришедшие от сервера.
- Можно моделировать последовательности: подключение, приветствие, тикеры, входящие сообщения, ошибки, reconnect. Тайминги задаём через таймеры, что позволяет тестировать обработку очередей и дублей.

Ещё нюансы по WebSocket

- Детерминизм: фиксируем интервалы, не опираемся на «рандом».
- Повторы и race conditions: явно тестируем дедупликацию и порядок доставки.
- Ошибки транспорта: эмулируем обрывы, 1006/1011, протокольные ошибки, неверный формат сообщения, размерные лимиты.

Практика подготовки HAR

- Очистите тестовые аккаунты и данные перед захватом, чтобы HAR не содержал чувствительных сведений и нестабильных идентификаторов.
- Уберите несущественные заголовки (например, динамические трейс‑идентификаторы), чтобы сопоставление работало по устойчивым ключам.
- Нормализуйте токены и даты: в HAR сохраняйте плейсхолдеры, а на подстановке заменяйте их тестовыми фиктивными значениями.
- Разделяйте сценарии: один HAR на «золотой путь», отдельные — на ошибки, пагинацию, пустые состояния.

Сопоставление запросов и ответов

- Ключ запроса: метод + путь + упорядоченные query + релевантные заголовки (например, Content-Type, Accept). Тело запроса хэшируем, если влияет на ответ.
- Версионирование API: добавляйте версию в ключ (например, /v1/, /v2/) — это позволяет держать несколько поколений контрактов параллельно.
- Порядковые ответы: если один и тот же запрос должен вернуть разные ответы последовательно (пагинация, long-polling), поддерживайте счётчик выдач.

Симуляция реальных условий

- Задержки: для сценариев таймаутов явно указывайте latency в моках.
- Ограничение пропускной способности: можно дозировать выдачу тела по частям, чтобы проверить прогрессы загрузки.
- Коды ошибок и нестандартные заголовки: покрывайте редкие статусы 429, 409, 422, а также корректность обработки Retry-After, ETag, Cache-Control.

Интеграция с CI

- Тестовые планы Xcode: заведите конфигурации под разные наборы моков и флаги.
- Параллельный прогон: моки лежат в каталоге, который не мутируется во время прогона; состояние тестов разделяйте через уникальные launchArguments.
- Кэш артефактов: HAR и сгенерированные моки версионируйте, добавляйте к ним семантические теги, чтобы легко откатываться и воспроизводить результаты.
- Метрики стабильности: отслеживайте процент флаки‑падений до/после внедрения моков, среднее время прогона, покрытие «краевых» сценариев.

Безопасность и конфиденциальность

- Не храните реальные токены, пароли, персональные данные в репозитории с моками.
- В процессе генерации заменяйте чувствительные поля плейсхолдерами; проверяйте, что HAR очищен от авторизационных заголовков, если они не нужны для логики.
- Для бинарных ответов убедитесь, что контент не содержит NDA‑материалов.

Поддержка и эволюция моков

- Когда API меняется, сначала обновляйте контракт, затем перезаписывайте HAR с новой версией, а старую временно сохраняйте для регресса.
- Используйте небольшие целевые HAR: чем меньше нерелевантных запросов, тем проще сопровождение.
- Ведите changelog на уровне моков: какие эндпоинты обновлены, какие статусы добавлены.

Типичные подводные камни

- Сопоставление «по частичному пути» ведёт к ложным совпадениям. Лучше строгие ключи и валидаторы тела.
- Динамические значения в запросах (timestamp, nonce) ломают ключ. Нормализуйте их в тестовой сборке или внедряйте функцию «обрезки» нестабильных полей.
- Зависимость тестов от порядка: делайте сценарии самодостаточными или явно сбрасывайте счётчики ответов.

Мини‑план внедрения

- Добавьте кастомный URLProtocol в тестовую сборку.
- Реализуйте загрузчик наборов моков по launchArguments.
- Настройте Proxyman, перехватите «золотые» сценарии, экспортируйте HAR.
- Напишите утилиту конвертации HAR → моки с индексом сопоставления.
- Покройте WebSocket через CFNotificationCenter с таймингами.
- Включите задержки/ошибки для сложных веток.
- Завезите конфигурации в тестпланы и CI.

Итоги

- Мокирование сети с помощью Proxyman + HAR и собственного URLProtocol делает UI‑тесты детерминированными и быстрыми.
- Управление сценариями через launchArguments упрощает вариативность: один тест — много веток.
- CFNotificationCenter позволяет реалистично воспроизводить WebSocket‑события без реального сервера.
- Экспорт трафика в HAR и автоматическая генерация моков сокращают ручную работу и исключают человеческий фактор.
- Такой подход особенно ценен, когда сервер недоступен, нестабилен или не имеет специальных тестовых API, а нам важно проверять именно клиентскую логику и соответствие контракту.

Прокрутить вверх