Всем привет! Меня зовут Александр и в hh.ru я занимаюсь решением инфраструктурных (и не только) задач, касающихся автотестирования. Ниже я опишу один из подобных кейсов. У нас в hh.ru более 370 микросвервисов. Классическая пирамида тестирования состоит из трех основных уровней: юнит-тесты, интеграционный слой, ui-тесты (e2e). Релизы сервисов проходят несколько раз в день. В вопросе интеграционных тестов было принято решение размещать их внутри сервиса отдельным модулем и запускать при очередных изменениях. При этом сами тесты проверяют эндпоинты тестируемого сервиса и иногда используют эндпоинты сервисов, которые с ним взаимодействуют.  Для визуализации зависимостей между сервисами есть отдельная самописная утилита, показывающая связи и топологию, поэтому процесс определения смежных сервисов больших затруднений не вызывает.  Но тут возникает следующий вопрос: как нам понять, все ли эндпоинты в сервисе проверяются и контролируют покрытие?  Из этого вопроса выросла задача оценки тестового покрытия интеграционными тестами. После изучения вопроса в качестве инструмента оценки был выбран JaCoCo. Так как он единственный из всех вариантов более-менее поддерживается. Он позволяет оценить покрытие кода в двух режимах: на соответствующей фазе сборки проекта работая как агент В случае оценки при сборке проекта JaCoCo необходимо подключить в pom-файл проекта. В нашем случае этот способ подходит только для unit-тестирования, так как для оценки не требуется наличие работающего приложения. Наши же интеграционные тесты запускаются как отдельное приложение и тестируют уже запущенный сервис. И тут нам пригодится механизм джава агентов, любезно предоставленных самой джавой. JaCoCo работает как агент Java. Он отвечает за инструментирование байт-кода во время выполнения тестов. JaCoCo углубляется в каждую инструкцию и показывает, какие строки выполняются во время каждого теста. Для сбора данных о покрытии JaCoCo использует ASM для инструментирования кода на лету, получая в процессе события от интерфейса JVM Tool. Если кратко, то суть следующая: при запуске jar нашего сервиса мы указываем jar агента, который встраивается в JVM и “слушает” код приложения. JaCoCo поставляется с агентом и консольной утилитой позволяющей подключиться к удаленному агенту, получить с него данные и сформировать отчет. Сам же отчет можно получить в нескольких форматах (html, xml, csv).  Инструкция по созданию отчета: Разворачиваем наш сервис, подключив агент JaCoCo: java -JVM_OPTS -javaagent:/docker-java-home/lib/jacocoagent/org.jacoco.agent.jar=address=*,port=4408,destfile=jacoco-it.exec,output=tcpserver -cp our/service/path  Выкачиваем JaCoCo и берем оттуда консольный клиент: wget https://search.maven.org/remotecontent?filepath=org/jacoco/jacoco/0.8.11/jacoco-0.8.11.zip -O jacoco-0.8.11.zip unzip -qo jacoco-0.8.11.zip -d jacoco cp jacoco/lib/jacococli.jar jacococli.jar Сбрасываем логи агента (так как при запуске сервиса осуществляется вызов кода сервиса, и он попадает в лог): java -jar jacococli.jar dump --address <service_name>.<stand_name> --port 4408 --destfile <dump_name>.exec --reset Запускаем интеграционные тесты Получаем логи агента: java -jar jacococli.jar dump --address <service_name>.<stand_name> --port 4408 --destfile <dump_name>.exec --reset Генерируем отчет по сервису: java -jar jacococli.jar report <dump_name>.exec --classfiles <path_to_service_target> --html <report_directory_name> --sourcefiles <path_to_service_source> Но что нам делать с отдельным отчетом по отдельному сервису? Хочется дальнейшей автоматизации.  JaCoCo очень хорошо интегрируется с Sonar. Для этого достаточно настроить сканер и с его помощью отправлять xml-отчет JaCoCo в сам Sonar. В результате у нас возник следующий процесс: Перед запуском интеграционных тестов мы запускаем тестируемый сервис с агентом JaCoCo Запускаем интеграционные тесты Получаем отчет в формате XML Отправляем отчет в Sonar Для отправки отчета используется консольная утилита SonarScanner. На основе сгенерированного JaCoCo отчета статистика отправляется в Sonar. Давайте посмотрим на команду отправки: sonar-scanner  -Dsonar.projectKey=project_key  -Dsonar.projectName=project_name  -Dsonar.host.url=https://sonar_url  -Dsonar.login=user_token  -Dsonar.coverage.jacoco.xmlReportPaths=jacoco_XML_report_path  -Dsonar.java.binaries=service_target_path  -Dsonar.sources=service_source_path Собственно, понятно, за что каждый параметр отвечает. Важное замечание: если в Sonar не существует указанного проекта, то он его создаст.  Казалось бы, все автоматизировано и можно спокойно получить анализ покрытия. Но разбираться в Sonar с покрытием каждого сервиса —  тоже весьма трудоемкий процесс, хочется и его как-то автоматизировать.  Можно напрямую обратиться к базе Sonar, где хранится вся статистика, но есть API, позволяющее получить интересующую нас информацию. Его мы и будем использовать. Давайте попробуем посчитать покрытие эндпоинтов сервиса нашими тестами. Для этого надо обратиться к четырем эндпоинтам Sonar: SONAR_URL/api/components/search?qualifiers=TRK — тут будем получать список всех проектов в Sonar SONAR_URL/api/measures/component?component=SERVICE_PROJECT_KEY&metricKeys=coverage — этим запросом получаем общее покрытие по сервису SONAR_URL/api/components/tree?component=SERVICE_PROJECT_KEY&qualifiers=FIL&q=Resource.java — здесь мы получаем список файлов-классов с описанием ресурсов нашего сервиса (у нас описание сервисов приведено к единому формату и все эндпоинты описываются в *Resource.java) SONAR_URL/api/measures/component?component=RESOURCE_FILE_KEY&metricKeys=coverage — этим запросом получаем покрытие по конкретному эндпоинту Алгоритм следующий: Получаем список проектов-сервисов Для каждого проекта получаем общее покрытие Также для каждого проекта получаем список файлов с описанием эндпоинтов сервиса Получаем покрытие по каждому файлу Выводим среднюю арифметическую оценку по покрытию эндпоинтов сервиса по формуле: сумма покрытия по всем файлам/количество файлов.  Реализуем алгоритм на python: В результате реализации мы получаем следующую статистику по каждому сервису: service1: общее покрытие: 11.4, покрытие по эндпоинтам: 32.7 service2: 25.4, покрытие по эндпоинтам: 42.9 service3: общее покрытие: 48.1, покрытие по эндпоинтам: 39.03 service4: общее покрытие: 36.9, покрытие по эндпоинтам: 59.89 service5: общее покрытие: 27.8, покрытие по эндпоинтам: 33.73 service6: общее покрытие: 72.1, покрытие по эндпоинтам: 82.75 service7: общее покрытие: 31.0, покрытие по эндпоинтам: 50.57 service8: общее покрытие: 17.7, покрытие по эндпоинтам: 15.5 service9: 66.1, покрытие по эндпоинтам: 39.7 service10: общее покрытие: 50.3, покрытие по эндпоинтам: 100.0 service11: общее покрытие: не покрыто тестами, покрытие по эндпоинтам: не покрыто тестами service12: общее покрытие: не покрыто тестами, покрытие по эндпоинтам: не покрыто тестами Обновление статистики мы сделали по расписанию — один раз в неделю. В результате проведенной работы получили следующее: регулярно получаем информацию о наличии интеграционных тестов по новым сервисам в нашей микросервисной архитектуре получили инструмент мониторинга покрытия как эндпоинтов, так и общего покрытия тестами логики сервиса Стоит отметить, что произвести оценку тестового покрытия таким образом можно не только на уровне интеграционных тестов, но также и UI. Для этого надо также запустить сервис с агентом JaCoCo и вместо интеграционных — запустить UI тесты. При этом отчеты агента можно рассматривать как по отдельности, так и свести их в один с помощью команды merge.  Head of QA