Владимир Янц, Badoo
Темы:
- Наш процесс разработки
- Юнит-тесты
- Интеграционные тесты
- Тесты на API
- Прогон тестов
К сроку выкатывания фичи добавляется:
- desktop web + два месяца
- mobile web + неделя
- iOS + месяц
- android + полгода
Разработчик отвечает за фичу от бэкенда до реализации на платформах. Интеграция бывает нескоро. Поэтому хочется сделать так, чтобы интеграция прошла хорошо.
Нужны автотесты!
Тесты на изолированные кусочки кода.
Сложно тестировать легаси. Для него делаем SoftMocks. Оно перехватывает все include/require в PHP-файле, подменяет подключаемый файл на другой.
Можно мокать любые методы: статические, приватные, финальные.
Проблема: SoftMocks расслабляют — можно писать плохо тестируемый код и всё равно покрыть его тестами. Решили правилами:
- новый код должен быть легко тестируемым с помощью PHPUnit
- SoftMocks — крайний случай, когда иначе нельзя.
Правила проверяются на код-ревью.
Мутационное тестирование.
-
берем код
-
берем code coverage
-
парсим код и генерируем мутации: меняем плюс на минус, true на false
-
для каждой мутации прогоняем набор тестов:
- если тесты упали — они хорошие
- если тесты успешно прошли — они недостаточно эффективны
Для PHP есть готовые решения: Humbug, Infection. Но они несовместимы с SoftMocks. Поэтому написали своё.
Тестируем работу нескольких компонентов в связке.
Стандартный подход:
- Поднимаем тестовую БД
- Заполняем её
- Запускаем тест
- Очищаем БД
Есть проблемы:
- нужно поддерживать актуальность содержимого базы
- требуется время на подготовку
- параллельные запуски делают тесты нестабильными и порождают дедлоки (когда тесты работают с одной и той же таблицей)
Решение: DBMocks
- Методы драйверов DB перехыватываются с помощью SoftMOcks на setUp теста
- из запроса вытаскиваются db + table
- создаются временные таблицы с такой же схемой
- все запросы идут во временные таблицы
- потом на tearDown они удаляются
Результаты:
- тесты не могут повредить данные в оригинальных таблицах
- тестируем совместимость запроса с версией MySQL.
- тесты вообще ходят в ту же БД по тому же адресу (просто таблица другая)
- тесты изолированы друг от друга
- имитируют клиентскую сессию
- умеют слать запросы к бэкенду
- бэкенд отвечает почти как реальному клиенту
Обычно такие тесты требуют авторизованного пользователя. Его нужно создать перед тестом и удалить после. Это порождает риск нестабильности, потому что при создании пользователя есть репликация и фоновые задачи.
Решение: пул тестовых пользователей.
- Нужен пользователь
- ищем в пуле
- если нет — создаём
- после теста очищаем, потому что тест загрязняет пользователя
Тестовые пользователи в одном окружении с реальными? Зачем вообще их тестировать на бою? Потому что devel ≠ prod. Надо изолировать, иначе пользователи будут флиртовать с кучей фиктивных Олегов.
Путь 1: флаг is_test_user
, по флагу изолируем внутри сервисов.
Путь 2: всех пользователей переселяем в условную Антарктиду и они там тусят друг с другом.
бэкдор для тестирования
- хорошо документированные методы API
- быстро и легко управляют данными
- пишут их бэкенд-разработчики
- применимы только к тестовым пользователям
Позволяют сменить пользователю неизменяемые данные, вроде даты регистрации.
Безопасность
- сетевая изоляция — доступно только из офисной сети
- с каждым запросом передаётся secret, валидность которого проверяем
- методы не работают с реальными пользователями
- программа BugsBounty на hackerone
- 100 000 юнит, 40 минут
- 6 000 интеграционных, 90 минут
- 14 000 API, 600 минут
Распараллелили тесты в TestCloud.
Как распределить тесты между потоками?
- поровну — фигня, все тесты разные, получатся неравные по времени части
- запустить несколько потоков и скармливать тесты по одному — тратим время на инициализацию PHP-окружения. Долго!
Как сделали:
- собираем статистику по времени прогона
- компонуем тесты так, чтобы один chunk прогонялся не более чем за 30 секунд
Проблема: медленные тесты занимают ресурсы, не давая выполняться быстрым. Т.е. API могут занять весь Cloud.
Решение: разделили Cloud
- часть 1 прогоняет только быстрые тесты
- часть 2 прогоняет любые тесты
Результат:
- Unit — 1 минута
- Интеграционные — 5 минут
- API — 15 минут
Какие тесты выполнять? Покажет code coverage.
- Получаем branch diff
- формируем список измененных файлов
- получаем список тестов, которые его покрывают
- запускаем прогон набора только из этих тестов
А где взять coverage?
- раз в сутки, ночь, полный прогон с test coverage
- результаты складываем в БД
Плюсы:
- прогоняем меньше тестов: меньше нагрузка на железо и быстрее обратная связь
- можем запускать тесты для патчей. Это позволяет быстро выкатить хотфикс. В патчах скорость важнее всего.
Минусы:
- Релизим бэкенд дважды в день, так что coverage актуален только для первого. Поэтому для билда прогоняем полный набор тестов.
- API-тесты генерируют слишком большой coverage. Для них этот подход не даёт большой экономии.
- все уровни пирамиды тестирования нужны и важны
- количество тестов ≠ качество. Делайте ревью кода тестов и мутационное тестирование.
- изолируйте тестовых пользователей от реальных.
- бэкдоры в бэкенде упрощают и ускоряют разработку тестов.
- собирайте статистику по тестам.
- Слайды: bit.ly/yants-HL18
- Наш протокол: «Как мы поддерживаем 100 разных версий клиентов в Badoo» / Ярослав Голуб
- SoftMocks: «SoftMocks: наша замена runkit для PHP 7» / Юрий Насретдинов, github.com/badoo/soft-mocks
- Badoo BugsBounty
- UI тесты: «Cross Platform Mobile Test Automation and Continuous Delivery» / Sathish Gogineni
- TestCloud: «Оптимальная параллелизация юнит- тестов или 17000 тестов за 4 минуты» / Илья Кудинов