Измерение уровня покрытия кода тестами в Raku | Code coverage level measurement in Raku
Легко заметить одну особенность в культуре написания модулей на Raku
и Perl
— почти всегда присутствует папка t
с авто тестами. Даже в маленьких проектах. Конечно, количество и качество тестов может варьироваться, но чаще всего они есть.
Когда мы говорим об авто тестировании, возникает желание как-то оценить «качество» написанных тестов. Одним из критериев может являться процент исполненных строк (веток) кода во время успешного прохождения тестов. На данный момент для Raku
это можно сделать двумя способами.
Первый — с помощью CommaIDE
(https://commaide.com), IDE для разработки на языке Raku
. CommaIDE
это отличная вещь, особенно, после последних релизов. Я настоятельно рекомендую её попробовать. В платной версии IDE есть возможность запустить тесты, и посчитать процент покрытия кода. Кроме того, для наглядности, код раскрасится в соответствующие цвета.
Второй — использовать модуль App::RaCoCo
(Raku Code Coverage). Он поставляет консольное приложение racoco
, которое запускает тесты, и подсчитывает процент покрытия. Сегодня хочется рассказать об этом модуле подробнее.
Как это работает в теории
Чтобы посчитать покрытие, нам нужно знать две вещи: какие именно строки кода потенциально могут выполниться, и фактически выполненные во время прохождения тестов.
NB: существуют несколько бекендов для компилятораRaku
(например,jvm
,js
,moarvm
). На данный момент предоставить всю необходимую информацию может толькоMoarVM
. Кроме того, более информативно считать не выполненные строки кода, а ветви. Например, в одной строкеsay 'second' if $first;
две ветви. К сожалению, сейчас дажеMoarVM
не предоставляет информации о выполненных ветвях. По-этому, далее будет говориться только о выполненных строках.
Как это работает на практике
Чтобы получить список потенциально исполняемых строк кода некоторого файла, нужно:
- получить байт-код этого файла. Его можно или найти в папке
lib/.precomp
, или скомпилировать самостоятельно. Например,raku -Ilib --target=mbc --output=result lib/source-file.rakumod
; - снять дамп с этого файла командой
moar --dump result
. Вхождения, начинающиеся со словаannotation
, заканчиваются номером строки, которая может быть исполнена в соответствующем модуле. Например:
[...] 00013 bindlex lex_Frame_9_self_obj, loc_19_obj 00014 param_sn loc_4_obj annotation: lib/source-file.rakumod (MyModuleName):9 00015 getcode loc_1_obj, Frame_10 00016 getcode loc_2_obj, Frame_11 [...]
Чтобы получить данные о том, какие строки выполнились, нужно:
- положить в переменные окружения специальный флаг
MVM_COVERAGE_LOG
. Его значением должен являться путь к файлу с будущими логами исполнения кода; - запустить тесты;
- разобрать файл с логами. Нас интересуют только строки с файлами из тестируемой библиотеки. Например:
HIT src/vm/moar/ModuleLoader.nqp 133 HIT src/vm/moar/ModuleLoader.nqp 5 HIT lib/source-file.rakumod (MyModuleName) 1 HIT lib/source-file.rakumod (MyModuleName) 9 HIT gen/moar/World.nqp 5240 HIT gen/moar/World.nqp 5240
Имея эти данные и немного знаний об арифметике можно посчитать результат.
Как это использовать
Рассмотрим несколько типичных вариантов использования racoco
.
MyModyle> racoco t/00-simple.t ................... ok t/01-difficult.t ................ ok All tests successful. Files=2, Tests=54, 3 wallclock secs Result: PASS # prove6 result Coverage: 70.6% # code coverage percentage MyModule> racoco --exec='prove --exec=raku' t/00-simple.t ................... ok t/01-difficult.t ................ ok All tests successful. Files=2, Tests=54, 3 wallclock secs Result: PASS # prove result Coverage: 70.8% MyModule> racoco --exec='prove6 -l' # this is for who do not write # 「use lib 'lib';」 in tests [...] MyModule> racoco --silent Coverage: 70.8% # just what it was all about MyModule> racoco --html # HTML with statistics and colored code Visualisation: file://.racoco/report.html Coverage: 70.8%
MyModule> racoco --/exec --html --color-blind # don't run tests, use the latest data # and color code for colorblind people
MyModule> racoco --fail-level=93 # the launch should fail [...] # in case of low coverage Coverage: 70.8% # exit code: 23 (93 - 70 = 23) MyModule> racoco --silent Coverage: 70.8% MyModule> racoco --silent --append --exec='prove6 xt' Coverage: 95.2% # combined coverage of both launches # similarly --exec='prove6 t xt' /root/ > racoco --lib='/home/user/raku/MyModule/lib' \ --exec='prove6 /home/user/raku/MyModule/t' # launch not from the module folder MyModule> racoco --raku-bin-dir='/home/user/hack-rakudo/install/bin' # explicit indication of the path to raku and moar
Неочевидный момент
racoco
может выбросить сообщение об ошибке про неочевидное содержимое папки .precomp
. Это происходит из-за того, что папка содержит подпапки для каждой использованной версии компилятора. В общем случае, пользователь может выбрать любую версию компилятора для запуска тестов. При этом, если нет необходимости, скомпилированные ранее файлы могут быть не обновлены. Таким образом, racoco
не может угадать какую папку анализировать. Для подобных случаев существует флаг --fix-compunit
, который удалит всё содержимое папки .precomp
самостоятельно (если вам дорого время компиляции, то можно уничтожить неактуальные версии скомпилированных файлов вручную).
Немного про CI
Невозможно говорить про авто тесты и покрытие кода и не заговорить про CI. В этом контексте измерение покрытия кода может использоваться для нескольких вещей:
- Падение сборки в случае снижения покрытия ниже определённого уровня;
- Падение сборки в случае снижения покрытия относительно предыдущих значений;
- Простое измерение покрытия кода как характеристики.
Для примера, рассмотрим то, как можно добавить шилдик об уровне покрытия кода в README
файл на GitHub
.
Есть замечательная статья о том, как настроить автоматическую проверку кода тестами с помощью GitHub Actions
для проектов на Raku
. Первый YAML из этой статьи отлично работает. Нужно заменить последний шаг (Run Tests), на следующую последовательность:
- Установить
App::RaCoCo
. Этот модуль намеренно не содержит никаких зависимостей, чтобы его установка занимала как можно меньше времени;- name: Install App::RaCoCO run: zef install --/test App::RaCoCo
- Запустить
racoco
;- name: Run App::RaCoCo run: racoco
- Вытащить результат работы
racoco
из файла.racoco/report.txt
. Нам нужна только первая строчка — процент покрытия. Убирается дробную часть и знак процента. Результат складываем во временную переменную окружения сборкиCOVERAGE
;- name: Discover Code Coverage Level run: | coverage=
head -1 .racoco/report.txt | sed 's/\.*//'echo "COVERAGE=$coverage%" >> $GITHUB_ENV shell: bash
- Для построения шилдика используем плагин
schneegans/dynamic-badges-action
;- name: Create Code Coverage Badge uses: schneegans/dynamic-badges-action@v1.1.0 with: auth: $\{\{ secrets.<YOUR_TOCKEN> }} gistID: <gist-ID> filename: <you-gist-file>.json label: Coverage message: $\{\{ env.COVERAGE }}
5. Добавляем строчку со ссылкой на шилдик в README.md.![badge](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/<user>/<gist-id>/raw/<you-gist-file>.json)
Пример почти такого YAML и итоговый шилдик можно посмотреть в репозитории App::RaCoCo
.
English version