В своей работе мы активно используем платформу SonarQube для поддержания качества кода на высоком уровне. При интеграции одного из проектов, написанном на VueJs+Typescript, возникли проблемы. Поэтому хотел бы рассказать подробней о том, как удалось их решить.

В данной статье речь пойдет, как писал выше, о платформе SonarQube. Немного теории — что это такое вообще, для тех, кто слышит о ней впервые:
SonarQube (бывший Sonar) — платформа с открытым исходным кодом для непрерывного анализа (англ. continuous inspection) и измерения качества кода.
Поддерживает анализ кода и поиск ошибок согласно правилам стандартов программирования MISRA C, MISRA C++, MITRE/CWE и CERT Secure Coding Standards. А также умеет распознавать ошибки из списков OWASP Топ-10 и CWE/SANS Топ-25 ошибок программирования.
Несмотря на то, что платформа использует различные готовые инструменты, SonarQube сводит результаты к единой информационной панели (англ. dashboard), ведя историю прогонов и позволяя тем самым увидеть общую тенденцию изменения качества программного обеспечения в ходе разработки.
Более подробно можно узнать на официальной сайте
Поддерживается большое количество языков программирования. Судя по информации из ссылки выше — это более 25 языков. Для поддержки конкретного языка необходимо установить соответствующий плагин. В community-версию входит плагин для работы с Javascript (в том числе typesсript), хотя в wiki написано обратное. За Javascript отвечает плагин SonarJS, за Typescript SonarTS соответственно.
Для отправки информации о покрытии используется официальный клиент sonarqube-scanner, который, используя настройки из config-файла, отправляет эти данные на сервер SonarQube для дальнейшей консолидации и агрегирования.
Для Javascript есть npm-обертка. Итак, начинаем пошаговое внедрение SonarQube в Vue-проект, использующий Typescript.
Для развертывания сервера SonarQube воспользуемся docker-compose.
sonar.yaml:
version: '1' services: simplesample-sonar: image: sonarqube:lts ports: - 9001:9000 - 9092:9092 network_mode: bridge
Запуск:
docker-compose -f sonar.yml up
После этого SonarQube будет доступен по адресу – http://localhost:9001 .

Пока в нем нет проектов и это справедливо. Будем исправлять данную ситуацию. За основу я взял официальный проект-пример для VueJS+TS+Jest. Склонируем его к себе:
git clone https://github.com/vuejs/vue-test-utils-typescript-example.git
Сначала нам нужно установить клиент SonarQube, который называется sonar-scanner, для npm есть обертка:
yarn add sonarqube-scanner
И сразу же добавим команду в scripts для работы с ним.
package.json:
{ … scripts: { ... "sonar": "sonar-scanner" ... }, … }
Далее, для работы сканера, нужно задать настройки проекта в специальном файле. Начнем с базовых.
sonar-project.properties:
sonar.host.url=http://localhost:9001 sonar.projectKey=test-project-vuejs-ts sonar.projectName=Test Application (VueJS+TS) sonar.sources=src # sonar.tests= sonar.test.inclusions=src/**/*tests*/** sonar.sourceEncoding=UTF-8
- sonar.host.url – адрес Sonar’а;
- sonar.projectKey – уникальный идентификатор проекта на сервере Sonar’а;
- sonar.projectName – его наименование, оно может быть изменено в любой момент, так как идентификация проекта производится по projectKey;
- sonar.sources – папка с исходниками, обычно это src, но может быть любым. Эта папка задается относительно рутовой папки, которой является папка откуда запущен сканер;
- sonar.tests – параметр, который идет в паре с предыдущим. Это папка, где находятся тесты. В данном проекте, нет такой папки, а тест находится рядом с тестируемым компонентом в папке ‘test‘, поэтому мы его пока проигнорируем и воспользуемся следующим параметром;
- sonar.test.inclusions – путь для тестов с использованием маски, может быть несколько элементов перечисленных через запятую;
- sonar.sourceEncoding – кодировка для исходных файлов.
Для первого запуска сканера все готово, кроме основного предшествующего действия: запуск самого тестового движка, для формирования им информации о покрытии, которую и будет в последствии использовать сканер.
Но для этого нужно настроить тестовый движок на формирование данной информации. В данном проекте тестовый движок — это Jest. И его настройки находятся в соответствующем разделе файла package.json.
Добавим эти настройки:
"collectCoverage": true, "collectCoverageFrom": [ "src/**/*", "!src/main.ts", "!src/App.vue", "!src/**/*.d.*", "!src/**/*__tests__*" ],
То есть задаем сам флаг необходимости вычисления покрытия и источник (вместе с исключениями), на основе которых оно будет формироваться.
Теперь запустим тест:
yarn test
Увидим следующее:

Причина в том, что в самом компоненте, как такового, кода нет. Исправим это.
HelloWorld.vue:
... methods: { calc(n) { return n + 1; } }, mounted() { this.msg1 = this.msg + this.calc(1); }, ...
Этого будет достаточно для расчета покрытия.
После перезапуска теста убедимся в этом:

На экране мы должны увидеть информацию о покрытии, а в папке проекта будет создана папка coverage с информацией о покрытии тестами в универсальном формате LCOV (LTP GCOV extension).
Gcov — свободно распространяемая утилита для исследования покрытия кода. Gcov генерирует точное количество исполнений для каждого оператора в программе и позволяет добавить аннотации к исходному коду. Gcov поставляется как стандартная утилита в составе пакета GCC.
Lcov — графический интерфейс для gcov. Он собирает файлы gcov для нескольких файлов с исходниками и создает комплект HTML страниц с кодом и сведениями о покрытии. Также генерируются страницы для упрощения навигации. Lcov поддерживает покрытие строк, функций, ветвлений.
После выполнения тестов информация о покрытии будет находится в coverage/lcov.info.
Нам надо сказать Sonar’у откуда ее взять. Поэтому добавим следующие строчки в его файл конфигурации. Но есть один момент: проекты могут быть мультиязычные, то есть в папке src находятся исходники для нескольких языков программирования и принадлежность к тому или иному, и в свою очередь использование того или иного плагина, определяется по его расширению. И информация о покрытии может хранится в разных местах для разных языков программирования, поэтому для каждого ЯП есть свой раздел для настройки этого. У нас проект использует Typescript, поэтому нам необходим раздел настроек именно для него:
sonar-project.properties:
sonar.typescript.coveragePlugin=lcov sonar.typescript.lcov.reportPaths=coverage/lcov.info
Все готово к первому запуску сканера. Хочу заметить, что проект в Sonar’е создается автоматически при первом запуске сканера для данного проекта. В последующие разы информация уже будет аккумулироваться, чтобы видеть динамику изменения параметров проекта во времени.
Итак, воспользуемся командой, созданной ранее в package.json:
yarn run sonar
Примечание: можно также воспользоваться параметром -X для более детального логирования.
Если запуск сканера был впервые, то сначала скачается бинарник самого сканера. После этого он запускается и начинает сканировать сервер Sonar’а на предмет установленных плагинов, вычисляя тем самым поддерживаемые ЯП. Также загружаются другие различные параметры для его работы: quality profiles, active rules, metrics repository, server rules.


Примечание: подробно на них мы останавливаться не будем в рамках данной статьи, но всегда можно обратиться в официальные источники.
Далее начинается анализ папки src на предмет наличия исходных файлов для всех (если не задан явно какой-то конкретный) поддерживаемых ЯП, с последующей их индексацией.

Далее идут другие различные анализы, на которых мы не заостряем внимание в данной статье (например, такие как: линтинг, определение дублирования кода и тд).
В самом конце работы сканера происходит агрегирование всей собранной информации, архивирование и отправка ее на сервер.
После этого мы можем уже посмотреть что получилось в вэб-интерфейсе:

Как видим, что-то получилось, и даже показывает какое-то покрытие, но оно не соответствует нашему Jest-отчету.
Давайте разбираться. Посмотрим на проект более детально, кликнем по значению покрытия, и "провалимся" в детализированный отчет по файлам:

Здесь мы видим помимо основного, исследуемого файла HelloWorld.vue, присутствует и файл main.ts, который и портит всю картину покрытия. Но как же так, мы его исключали из расчета покрытия. Да, все правильно, но это было на уровне Jest, но сканер его проиндексировал, поэтому он попал в его расчеты.
Давайте исправим это:
sonar-project.properties:
... sonar.exclusions=src/main.ts ...
Хочется сделать уточнение: помимо тех папок, которые заданы в данном параметре, также добавляются все папки, перечисленные в параметре sonar.test.inclusions.
После запуска сканера видим уже корректную информацию:


Разберем следующий момент – Quality profiles. Я говорил выше о поддержке Sonar’ом несколько ЯП одновременно. Вот это как раз мы и наблюдаем. Но мы знаем, что проект у нас написан на TS, поэтому зачем напрягать сканер лишними манипуляциями и проверками. Язык для анализа зададим через добавление еще одного параметра в файл конфигурации Sonar’а:
sonar-project.properties:
... sonar.language=ts ...
Снова запустим сканер и посмотрим результат:

Покрытие пропало вовсе.
Если посмотрим в лог сканера, то можем увидеть следующую строчку:

То есть файлы нашего проекта просто не были проиндексированы.
Ситуация следующая: официально поддержка VueJs есть в плагине SonarJS, который отвечает за Javascript.

Но этой поддержки нет в плагине SonarTS для TS, о чем заведен официальный тикет в баг-трекере Sonar’а:
Вот некоторые ответы одного из представителей со стороны разработчиков SonarQube, подтверждающий этот факт.


Но у нас же все работало, возразите Вы. Да, так и есть, давайте попробуем немного “похакерить”.
Если есть поддержка .vue-файлов Sonar’ом, то давайте попробуем сказать ему чтобы он их рассматривал как Typescript.
Добавим параметр:
sonar-project.properties:
... sonar.typescript.file.suffixes=.ts,.tsx,.vue ...
Запустим сканер:

И, вуаля, все вернулось на круги своя, и с одним профилем только для Typescript. То есть удалось решить проблему в поддержке VueJs+TS для SonarQube.
Попробуем пойти дальше и немного улучшим информацию о покрытии.
Что же мы сделали на данный момент:
- добавили в проект Sonar-сканер;
- настроили Jest для формирования информации о покрытии;
- сконфигурировали Sonar-сканер;
- решили проблему поддержки .vue-файлов + Typescript.
Кроме покрытия тестами есть другие интересные полезные критерии качества кода, например, дублирование кода и количество строк (участвует в расчете коэффициентов, связанных со сложностью кода) проекта.
В текущей реализации плагина для работы с TS (SonarTS) не будет работать CPD (Copy Paste Detector) и подсчет строк кода .vue-файлов.
Для создания синтетической ситуации по дублированию кода, просто задублируем файл компонента с другим именем, также добавим в код main.ts функцию-пустышку и задублируем его с другим именем. Чтобы проверить дублирование как в .vue, так и в .ts -файлах.
main.ts:
... function name(params:string): void { console.log(params); } ...
Для этого необходимо временно закоментировать строчку конфигурации:
sonar-project.properties:
... sonar.exclusions=src/main.ts ...
Перезапустим сканер вместе с тестированием:
yarn test && yarn run sonar
У нас конечно упадет покрытие, но сейчас нам это не интересно.
В разрезе дублирования строк кода увидим:

Для проверки воспользуемся CPD-утилитой – jscpd:
npx jscpd src

Для строк кода:

Возможно, это решится в будущих версиях плагинов SonarJS(TS). Хочу заметить, что они постепенно начинают сливать эти два плагина в один SonarJS, что, думаю, правильно.
Теперь хотелось рассмотреть вариант улучшения информации о покрытии.
Пока мы видим покрытие тестами в процентном отношении, по всему проекту, и по файлам в частности. Но есть возможность расширить этот показатель информацией о количестве unit-тестов по проекту, а также в разрезе файлов.
Есть библиотека, которая умеет Jest-репорт конвертировать в формат для Sonar’а:
generic test data — https://docs.sonarqube.org/display/SONAR/Generic+Test+Data.
Установим эту библиотеку к себе в проект:
yarn add jest-sonar-reporter
И добавим его в конфигурацию Jest:
package.json:
… "testResultsProcessor": "jest-sonar-reporter" …
Теперь выполним тест:
yarn test
После чего в корне проекта будет создан файл test-report.xml.
Задействуем его в конфигурации Sonar’а:
sonar-project.properties:
… sonar.testExecutionReportPaths=test-report.xml …
И перезапустим сканер:
yarn run sonar
Посмотрим, что поменялось в интерфейсе Sonar’а:

И ничего не поменялось. Дело в том, что Sonar не рассматривает файлы, описанные в Jest-репорте, как файлы unit-тестов. Для того, чтобы исправить эту ситуацию задействуем параметр конфигурации Sonar sonar.tests, в котором явно укажем папки с тестами (она у нас пока одна):
sonar-project.properties:
… sonar.tests=src/components/__tests__ …
Перезапустим сканер:
yarn run sonar
Посмотрим, что поменялось в интерфейсе:

Теперь мы увидели количество наших unit-тестов и, провалившись по клику внутрь, можем посмотреть распределение этого числа по файлам проекта:

Заключение
Итак, мы рассмотрели инструмент для непрерывного анализа SonarQube. Успешно интегрировали в него проект, написанный на VueJs+TS. Решили некоторые проблемы совместимости. Повысили информативность показателя о покрытии тестами. В данной статье мы рассмотрели лишь один из критериев качества кода (возможно, один из основных), но SonarQube поддерживает и другие критерии качества, включая тестирование на безопасность. Но не все эти возможности в полном объеме доступны в community-версии. Одна из интересных и полезных возможностей — это интеграции SonarQube с различными системами управления репозиториями кода, например, такие как GitLab и BitBucket. Чтобы не допустить merge pull(merge) request’а в основную ветку репозитория при деградации покрытия. Но это история уже совершенно другой статьи.
PS: Все, что описано в статье в виде кода доступно в моем форке.
ссылка на оригинал статьи https://habr.com/ru/company/odin_ingram_micro/blog/488242/
Добавить комментарий