Написание автотестов — одна из наиболее эффективных практик, которая позволяет проверять работоспособность всех компонентов сервиса и своевременно обнаруживать любые сбои. Но писать много автотестов — не всегда верный подход: тестов должно быть не много, а достаточно. Иначе время и силы инженеров по тестированию и автоматизации будут тратиться напрасно. Но как понять, что автотестов достаточно, и, главное, быть уверенным, что обновление или добавление новых методов внутри проекта не создаст «неотслеживаемые бреши»? Очевидно, что для этого нужна система анализа покрытия автотестами.
Мы продолжаем серию статей об автотестах в ОК (материалы по этой теме мы можете почитать здесь, здесь и здесь). И сегодня рассказываем на примере анализа покрытия автотестами API, как с этими задачами справляется команда ОК.
Статья подготовлена по мотивам совместного доклада руководителя команды автоматизации тестирования Эмилии Куцаревой и инженера по автоматизации тестирования в Одноклассниках Елизаветы Андреевой «API: анализ покрытия и автотесты» на ИТ-конференции «Стачка».
Про автотесты в ОК
ОК — большая социальная сеть с огромным бэкендом, который включает тысячи методов и сервисов. Нам критически важна постоянная доступность (uptime) и отказоустойчивость продукта, поэтому для отслеживания работоспособности всех его компонентов мы стараемся покрыть всё нужное тестовыми сценариями, а ручные тесты по возможности заменяем автоматизированными.
Сейчас по всем основным платформам ОК у нас около 10 тысяч автотестов:
web — около 3150;
API — около 2500 (+1500 автосгенерированных);
Android — около 1400;
mobile web — около 1200;
iOS — около 1000.
По количеству вызовов и, соответственно, приоритету, API — платформа номер один. Это обусловлено тем, что API используется как другими платформами, так и внешними разработчиками.
Вместе с тем, ОК не только большой, но и «живой» продукт — мы непрерывно дорабатываем и улучшаем соцсеть на всех платформах, чтобы соответствовать запросам пользователей. Подход, при котором можно один раз написать автотесты и успешно их использовать на всем жизненном цикле продукта — не наш случай. Поэтому мы выделяем на работу с автотестами много ресурсов.
Нюанс в том, что наши ресурсы не безграничны, то есть нам важно не только контролировать, чтобы в каждый момент времени тестами были покрыты все приоритетные методы, но и понимать, когда тестов уже достаточно. И эта задача невыполнима без анализа покрытия.
Об этом подробнее на примере анализа покрытия API.
Методы оценки покрытия
Для понимания необходимого покрытия API и оценки его актуальности мы берем информацию из различных источников.
Test Management System. При работе с тест-кейсами в TMS мы можем добавлять отметки о том, что покрыто автотестами, а что нет. Подход хорош тем, что позволяет учесть всю бизнес-логику, но для получения максимально объективной картины нужны дополнительные способы анализа. К тому же чаще всего разметка тест-кейсов делается вручную, что отнимает достаточно много времени.
Анализ действий реальных пользователей. Мы можем анализировать действия пользователей, например, смотреть, с какими блоками сайта или приложений люди взаимодействуют чаще, чтобы выявить наиболее используемые API-методы. На основании этой информации мы можем приоритизировать покрытие тестами.
Автоматизированные тесты API и UI. Мы можем смотреть, какие методы API вызывают автотесты, и таким образом проверять, что именно у нас уже покрыто автотестами.
Чтобы не упускать из вида новые методы и отслеживать задачи на покрытие, мы добавили следующие опции.
Джобы в TeamCity. С помощью джоб в TeamCity мы мониторим наличие новых и измененных методов.
Jira-таски. При появлении нового метода автоматически создается задача в Jira на покрытие автотестами.
Ручной анализ. С помощью различных настроенных фильтров, установленных тегов, собранных дашбордов в Jira и даже обычных таблиц мы также следим за покрытием методов.
Но такого мониторинга было недостаточно для обработки огромного бэклога методов и понимания, что нужно покрывать автотестами в первую очередь. Соответственно, в подобной реализации отсутствует и конечная цель: непонятно, когда можно остановиться и сфокусироваться на работе только с новыми фичами.
Немного предыстории: старый подход к расстановке приоритетов
Очевидно, что в таком большом проекте, как ОК, есть огромное количество API методов, и мы не можем покрывать их автотестами в произвольном порядке — это изначально ошибочный путь. Поэтому приоретизация у нас была, но довольно простая.
В первую очередь мы смотрели на наличие тестов на каждый отдельный метод. Для методов, на которые нет тестов, получали количество вызовов за последний месяц. В итоге формировался список методов, требующих покрытия, и мы ранжировали их по количеству вызовов. Логика следующая:
Больше всех вызывается = выше приоритет покрытия
Но в этом подходе есть недостатки. Выделим основные.
Старые тесты = тесты, обеспечивающие достаточную проверку. Текущий подход подразумевает, что существующие тесты обеспечивают достаточное покрытие методов. Поэтому на ранжирование попадают только методы, не имеющие тестов. Получается, что, если в методе поменялась сигнатура, например, были добавлены новые параметры, это никак будет не учитываться и написание новых тестов под эти параметры не попадет в приоритет.
Считаем все сервисы одинаково приоритетными. Ранжирование не учитывает критичность сервиса. Методы, которые работают с чувствительными данными пользователей никак не выделяются в списке на покрытие. Например, один метод может работать с сообщениями, а другой — с деньгами пользователей, но вызываться кратно реже, чем первый, и поэтому находиться ниже в очереди на покрытие, что некорректно с точки зрения продукта.
Все еще очень много задач. Список методов на покрытие автотестами может быть длинным и по-прежнему не дает понимания по приоритетам. Кроме этого, нет ответа на вопросы: «Когда покрытие тестами на метод можно считать достаточным? Можно ли остановиться и заняться другими задачами?».
Приоритеты покрытия: новая метрика
Со временем мы приняли решение пересмотреть подход к определению приоритета покрытия и исключить упомянутые недостатки.
Теперь мы оцениваем сразу комплекс параметров для каждого метода и назначаем за них различные коэффициенты (от 0 до 3).
Параметр | Коэффициент |
Наличие тестов на метод | 0/1 |
Количество вызовов метода | 0-1 |
Критичность сервиса | 0-3 |
Количество существующих тестов на метод больше или равно количеству параметров в методе | 0/1 |
Публичность API метода для внешнего пользователя (публичность документации) | 0/1 |
Возможность работы из разных сессий | 0/1 |
Наличие ограничений приватности | 0/1 |
Рассмотрим подробнее добавленные параметры.
Наличие тестов на метод. Если тестов еще нет — коэффициент выше, если уже есть — ниже.
Количество вызовов метода. Чем больше вызовов метода, тем критичнее его покрытие и, соответственно, выше коэффициент.
Критичность сервиса. Теперь метод по работе с деньгами пользователя из примера прошлого раздела станет выше в топе.
Количество существующих тестов на метод. У нас это значение соотносится с количеством параметров в методе (за редкими исключениями). Добавлено это, чтобы не забывать про изменения сигнатур методов.
Доступность метода внешнему пользователю. Такие методы, естественно, нужно покрыть в первую очередь. Раньше это учитывалось, но нигде не фиксировалось (кроме головы инженера по тестированию).
Возможность работы из разных сессий. Мы постарались пересмотреть то, как пишем тесты сейчас, и поняли, что смотрели на невозможность вызова метода из разных сессий и меньше обращали внимание на дополнительные проверки работы тех методов, которые, наоборот, допускают работу из них.
Наличие ограничений приватности. Здесь также коэффициент назначается в зависимости от наличия ограничений.
Расширение перечня учитываемых параметров и добавление к ним коэффициентов позволило нам изменить логику расстановки приоритетов. Так, сейчас для каждого метода:
Приоритет покрытия = сумма коэффициентов
При таком подходе мы сводим к минимуму влияние человеческого фактора и безосновательную рандомизацию.
Примечание: Выработанный нами подход универсален. Поэтому при необходимости применить подобную схему оценки приоритетов можно в каждом проекте — для этого достаточно переработать ее с учетом метрик, актуальных именно для вас.
Стек технологий проекта
Наш проект с автотестами написан на Java с использованием JUnit5, Spring, Maven, Hamcrest.
Ключевым инструментом в нашей реализации является кастомный API клиент. Это понятный фреймворк для разработчиков и QA-инженеров, с помощью которого можно легко добавить новый метод или изменить существующий.
Многие действия в нем автоматизированы. Например, при создании нового метода в специальной аннотации, с помощью которой помечаем параметры, можно указать важность нового параметра. После этого не надо писать проверки на его наличие в запросе — это будет выполнено на уровне фреймворка.
Кроме этого, клиент, который используется в автотестах, автоматически генерируется на каждую сборку проекта API. Если метод был добавлен в основные несколько файлов, то он появится в собранном клиенте, и можно будет писать на него автотесты, подключив новую версию клиента в проект.
Наша документация apiok.ru тоже создается автоматически с помощью Reflection API. По определенным аннотациям создаются шаблоны. Единственное, что остается заполнить человеку — словесное описание. Типы возвращаемых значений, необходимость параметров и сессий генерируются автоматически.
Хочется отдельно отметить неочевидный плюс кастомного клиента: мы можем использовать его не только для написания API тестов, но и в автоматизации на других платформах: на Web, Mobile Web, Android и iOS.
Измерение покрытия: Inspector
В погоне за автоматизацией «всего, что рационально и оправданно автоматизировать», мы не могли не автоматизировать и оценку приоритизации покрытия.
Для этих задач мы используем разработанный нами сервис Inspector, который выполняет анализ покрытия API автотестами и показывает, какие методы не покрыты.
Inspector тоже написан на Java, как и проект с тестами, с использованием Spring, Gradle, Angular. На данный момент он работает с нашей старой формулой назначения приоритетов.
Один из главных «козырей» Inspector — удобный наглядный интерфейс.
Примечание: В нашем инструменте уже реализован удобный UI, но при разработке своего решения в первую очередь имеет смысл сделать рабочий прототип с MVP, который уже позволит вам сократить ручную работу и получать первые результаты анализа покрытия.
На главной странице мы можем увидеть:
список отчетов по отслеживаемым тестовым запускам на трех средах: продакшен, препрод и тестовая среда;
количество тестовых методов, запущенных в прогоне;
количество методов (вызванных в тестовом запуске/прогоне и общее количество).
Можно открыть более подробный отчет с тестами и посмотреть, какие запросы к API выполнялись в них. Кроме этого, собирается метаинформация, поэтому отображаются даже теги, поставленные на тесте. Есть связки с другими сервисами, например, отсюда можно перейти сразу в IDE на нужный тест и посмотреть код или открыть отчет с шагами выполнения теста.
Данные запросов/ответов из API-тестов Inspector предоставляет в формате удобной и структурированной таблицы.
В Inspector нам также доступны отчеты по покрытию методов API тестами в раундах. Причем всю информацию мы можем фильтровать по наличию тестов и категориям, а также настраивать отображение количества вызовов методов по нужным платформам за последний месяц.
Кроме этого, отчеты можно экспортировать в Excel для удобной работы с полученными результатами. При переносе сохраняются установленные фильтры.
Техническая реализация
«Под капотом» Inspector интегрирован с нашим Data Warehouse. В ОК DWH отвечает за хранение, обработку и анализ данных об активности пользователей на портале, а также формирование KPI для отдела аналитики и менеджеров проектов. Но в рамках интеграции с Inspector DWH используется для получения данных о количестве вызовов методов.
Интеграция Inspector с нашим DWH реализована через Druid — систему, построенную, чтобы обеспечить быстрый (real-time) доступ к большим наборам редко меняющихся данных. Обращение к Druid осуществляется с помощью запроса в формате JSON методом POST по ID приложения и временному диапазону.
Алгоритм получения и обработки данных сейчас состоит из нескольких шагов.
В проекте с тестами инициализируется листенер, который управляет записью данных в Inspector.
Реализован интерфейс InspectorTestExecutionListener, который имплементирует TestExecutionListener из JUnit5. TestExecutionListener позволяет встроиться в нужное место выполнения тестовых наборов и даже собирать метаинформацию. При старте набора тестов передаем сервису название тестового прогона с помощью переопределения testPlanExecutionStarted.
Во время прогона автотестов все запросы к API перехватываются и сохраняются.
Когда начинается отдельный тест, добавляем его и список тегов в InspectorRequestHolder, который служит контейнером для хранения перехваченных HTTP-запросов.
В методе startTest просто присваиваем имя и теги теста.
По завершению теста сохраняем результаты в список, очищаем переменные, которые относятся к исполнению текущего теста.
После завершения прогона в Inspector грузится информация о тестах и методах.
Генерируется и отправляется отчет в формате JSON, в который включено название прогона тестов, текущая дата, информация по тестам, которая включает метаинформацию и HTTP-запросы, сделанные в рамках теста, и номер версии нашего кастомного клиента API.
В сервисе данные парсятся и раскладываются в БД: тесты отдельно, методы отдельно, раунд с подсчетом coverage отдельно.
Берется список открытых/закрытых методов из репозитория с документацией.
К методам, которые получены из прогона, добавляются все методы, присутствующие в API. На их основе будет рассчитано покрытие.
Количество вызовов по платформам раз в день загружается на Druid с данными по вызовам за последний месяц.
Из этих таблиц на UI рисуются раунды/тесты/методы.
Будущее Inspector’а
Сейчас Inspector хорошо справляется со всеми текущими задачами: значительно упрощает нам работу с автотестами и анализ покрытия. Но в наших планах сделать инструмент еще удобнее и функциональнее. Так, мы планируем:
Добавить работу с новой формулой выбора приоритетов. Чтобы иметь возможность автоматически рассчитывать приоритет покрытия на основании суммы коэффициентов.
Доработать UI. Кроме изменений, касающихся новой формулы, хотим добавить возможность QA-инженерам создавать фильтры по командам и отображать в интерфейсе только нужные категории.
Подключить CI/CD. Чтобы оценка покрытия происходила автоматически при написании автотестов.
Планы по развитию автоматизации API-платформы
У нас много методов, и их количество постоянно растет. Поэтому недавно мы внедрили автоматическую генерацию автотестов API. Это помогло нам снизить нагрузку на специалистов и значительно повысить коэффициент покрытия. При этом автогенерация не затрагивает методы, которые относятся к бизнес-логике — эти задачи останутся в зоне ответственности наших QA-инженеров.
Теперь остановимся подробнее на том, в каком направлении мы планируем развивать автоматизацию нашей API-платформы.
Автоматизация итераций по анализу покрытия. Мы хотим сделать автоматический анализ покрытия, который будет обновляться с определенной частотой. Сейчас за эту задачу отвечает core QA-инженер, который выполняет часть работы вручную. Доработка Inspector’а отчасти сделает работу core QA-инженера легче, но мы также хотим, чтобы любой QA-инженер и руководитель группы тестирования мог с помощью сервиса отслеживать текущее состояние своего проекта.
Мониторинг: метрики и алерты. Внедрение мониторинга позволит нам в реальном времени отслеживать бизнес- и технические метрики, которые могут быть полезны как, например, техническому директору, так и разработчику. В свою очередь, подключение алертов даст нам возможность сразу уведомлять QA-инженеров о появлении новых приоритетных методов, которые требуют покрытия автотестами — это сделает отслеживание контролируемым и понятным.
Внедрение SLA. Мы хотим прийти к ситуации, при которой сможем гарантировать определенные фиксированные сроки покрытия API методов автотестами. Сейчас мы также назначаем дедлайны, но пока не фиксируем их четко. С помощью средств автоматизации мы хотим это регламентировать.
Quality Gate. В перспективе мы хотим внедрить Quality Gates. Таким образом, мы сможем, например, блокировать пулл-реквесты в основную ветку, если код не покрыт автотестами в достаточной мере.
Автоматизация тестирования связи API и мобильных клиентов. Пользователи Android составляют 53% месячной аудитории ОК, а iOS — 9%. При этом и Android, и iOS платформы завязаны на вызове API. Поэтому нам важно проверять, как новые релизы для мобильных клиентов работают в связке с API. У нас уже есть опция проверки релизов для Android с помощью запуска автотестов API на тестовой среде, но такой запуск делается пока ещё не автоматически.
Таким образом, нашу платформу автоматизации в перспективе ждет значительная оптимизация, которая позволит работать с автотестами еще удобнее и эффективнее.
Заключение
Для корректной работы с автотестами важно не только думать о написании самих тестов, но и заботиться о достаточном покрытии — без этого невозможно гарантировать качество продукта, особенно при его динамичной доработке и регулярном обновлении методов.
Наряду с этим надо думать и о корректном назначении приоритетов — иначе можно столкнуться с ситуацией, при которой методы, имеющие критическое значение, будут «в режиме вечного ожидания».
Более того, на основе опыта работы с тысячами автотестов мы пришли к концепции, что автоматизировать нужно все, что возможно и экономически целесообразно, а приоритет покрытия определять с учетом критичности и срочности. Следование этому подходу дает возможность исключить человеческий фактор, снизить нагрузку на специалистов, а также сделать все процессы прозрачными и предсказуемыми.