Ранее мы научились писать модульные тесты на языке Go и измерять степень покрытия кода тестами. Также недавно мы познакомились с GitHub Actions и узнали, как с его помощью автоматически собирать и тестировать проект. Казалось бы, проверять code coverage при помощи GitHub Actions должно быть проще простого. Используем материалы двух предыдущих статей, и готово! Но если бы все было так банально, вы бы сейчас не читали эти строки.
Оказывается, разработчиков редко волнует значение code coverage в моменте. Что на самом деле интересно, это то, как pull request меняет покрытие кода по сравнению с веткой master. Еще интереснее, какие строки кода станут покрыты или перестанут быть покрыты тестами, если смержить заданный pull request. Связано это с тем, что в обычно решается задача либо увеличить покрытие кода тестами, либо убедиться, что новый код имеет адекватное покрытие. Увы, из коробки в языке Go нельзя получить ответы на эти вопросы. Однако данный функционал реализован в сервисе codecov.io . Сервис бесплатен для открытых проектов и стоит вполне разумных денег для закрытых.
За основу был взят демонстрационный проект к заметке про модульные тесты. В первую очередь он был переведен на модули , само собой разумеется, с вендорингом всех зависимостей. Также версия minimock была обновлена на последнюю.
Далее настраиваем GitHub Actions, как-то так:
on :
push :
branches :
— master
pull_request :
env :
GO_VERSION : 1.14
jobs :
all-checks :
name : checks
runs-on : ubuntu-latest
steps :
— name : set up go $ { { env.GO_VERSION } }
uses : actions/setup-go@v2
with :
go-version : $ { { env.GO_VERSION } }
— name : Check out code into the Go module directory
uses : actions/checkout@v1
with :
fetch-depth : 1
— name : Install build tools
run : go install github.com/gojuno/minimock/v3/cmd/minimock
— name : Run go generate
run : go generate ./ …
— name : Run tests
run : go test -count= 1 -v ./ …
— name : Check code coverage
run : |
go test -coverprofile=coverage.tmp.out ./…
cat coverage.tmp.out | grep -v _mock.go > coverage.out
go tool cover -html=coverage.out -o coverage.html
— name : Add coverage.out to artifacts
uses : actions/upload-artifact@v1
with :
name : coverage-out
path : ./coverage.out
— name : Add coverage.html to artifacts
uses : actions/upload-artifact@v1
with :
name : coverage-html
path : ./coverage.html
Тут уже есть несколько нюансов.
Во-первых, утилита minimock так просто не установится командой go install
в проекте с вендорингом. Ведь код нашего проекта от этой утилиты, строго говоря, не зависит, поэтому незачем качать ее в каталог vendor. Но именно там ее попытается искать команда go install
. В мире Go эта проблема решается созданием файла с именем tools.go:
package tools
import _ «github.com/gojuno/minimock/v3/cmd/minimock»
Вообще-то, подобный импорт в Go завершится с ошибкой. Но поскольку tools.go находится под билд-тэгом, то файл никогда не соберется. А раз файл есть и в нем есть импорты, go mod vendor
скачает minimock и положит его в каталог vendor. Ужасный хак, но работает.
Во-вторых, устанавливать Go обязательно нужно при помощи actions/setup-go@v2
с акцентом на v2
. Если воспользоваться v1
, то $GOPATH/bin
не будет добавлен в переменную окружения $PATH
. В итоге будет непросто вызвать minimock, а он нужен для генерации mock’ов при помощи go generate
.
Дальше ничего особенного, если не считать сохранения файлов coverage.out и coverage.html в артефактах. Их можно будет посмотреть в браузере после того, как джоба завершится. Этот момент ничем не примечателен, просто раньше мы не использовали артефакты в GitHub Actions .
Если вас не очень интересует динамика изменения code coverage, вы не хотите платить за codecov.io, или вы просто не очень любите зависеть от лишних сервисов, то на этом шаге можно остановится. Просто проверить, что код покрыт не менее чем на N%, можно таким скриптом:
import sys
import argparse
import subprocess
import re
parser = argparse . ArgumentParser ( description = ‘Check code coverage’ )
parser . add_argument (
‘-m’ , ‘—minimum’ , metavar = ‘N’ , type = float , required = True ,
help = ‘minimum code coverage, percents’ )
parser . add_argument (
‘-f’ , ‘—fname’ , metavar = ‘F’ , type = str , required = True ,
help = ‘path to golang coverage.out file’ )
args = parser . parse_args ( )
fname = args. fname
required = args. minimum
cmd = «go tool cover -func={}» . format ( fname )
out = subprocess . check_output ( cmd , shell = True ) . decode ( ‘utf-8’ )
m = re . search ( «»»(?si)total:.*?([ d . )]+) % $»»» , out )
coverage = float ( m. group ( 1 ) )
print ( «Current coverage: {:.2f}, required: {:.2f}» . format (
coverage , required ) )
if coverage >= required:
print ( «Check passed.» )
sys . exit ( 0 )
else :
print ( «CHECK FAILED!» )
sys . exit ( 1 )
Пример его использования:
Дописываете команду в приведенный выше YAML-файл, и проблема решена.
Если же вы хотите воспользоваться именно codecov.io, то необходимо положить в репозиторий проекта bash-скрипт для загрузки отчетов в сервис. После этого правим YAML’ичек таким образом:
go tool cover -html=coverage.out -o coverage.html
— name : Upload coverage report to codecov.io
run : ./scripts/codecov-upload.sh -f ./coverage.out ⏎
-y ./codecov.yml -n coverage-report -F
— name : Add coverage.out to artifacts
…
Файл codecov.yml пока что создаем чисто символический:
layout : «diff, flags, files»
Наконец, добавляем в репозиторий приложение Codecov . Этот шаг не обязательный, но он добавит в проект дополнительные проверки. На них можно повесить рестрикты в настройках репозитория.
С приложением или без, к вам в pull request’ы начнет приходить бот и оставлять комментарии о том, как pull request’ы меняют code coverage. По ссылкам из комментария можно получить более подробную информацию по отдельным файлам и строчкам в них. Как все это хозяйство выглядит в итоге, можно посмотреть в этом пулл реквесте .
И не забываем самый главный шаг — добавить бейджики в README.