Инструменты покрытия кода помогают понять, какая часть кодовой базы выполняется (или, как еще говорят, покрывается) при выполнении данного набора тестов. Какое-то время Go поддерживал измерение покрытия кода на уровне пакета, введенное в Go 1.2, она включалась флагом команды go test -cover.


Это хорошо работает в большинстве случаев, но при разработке больших приложений обнаруживаются недостатки. Для больших приложений разработчики часто пишут интеграционные тесты, которые проверяют поведение всей программы (в дополнение к модульным тестам на уровне пакета).


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


Двоичные файлы интеграционных тестов создаются командой go build, а не go test, поэтому инструментарий Go до сих пор не предоставлял простого способа сбора профиля покрытия этих тестов.


С версии Go 1.20 программы с инструментированием покрытия можно создавать командой go build -cover, а затем, чтобы расширить область покрытия, передавать эти инструментированные двоичные файлы в интеграционный тест.


Ниже приводим пример того, как работают эти новые функции, расскажем о вариантах применения и рабочем процессе сбора профилей покрытия из интеграционных тестов.


Пример


Возьмем очень маленький пример программы, напишем для нее простой интеграционный тест, а затем соберем профиль покрытия из интеграционного теста.


Воспользуемся для этого инструментом обработки Markdown mdtool отсюда. Это демо-программа, показывающая, как клиенты могут использовать библиотеку преобразования markdown в HTML gitlab.com/golang-commonmark/markdown.


Установка mdtool


Загрузим конкретную версию mdtool, чтобы эти шаги можно было повторить:


$ git clone https://gitlab.com/golang-commonmark/mdtool.git
...
$ cd mdtool
$ git tag example e210a4502a825ef7205691395804eefce536a02f
$ git checkout example
...
$

Простой интеграционный тест


Напишем простой интеграционный тест mdtool; он создаст двоичный файл mdtool и запустит его на наборе входных файлов markdown. Этот очень простой скрипт запускает двоичный файл mdtool для каждого файла из каталога тестовых данных, чтобы убедиться, что инструмент выдает некий результат и не падает.


$ cat integration_test.sh
#!/bin/sh
BUILDARGS="$*"
#
# Terminate the test if any command below does not complete successfully.
#
set -e
#
# Download some test inputs (the 'website' repo contains various *.md files).
#
if [ ! -d testdata ]; then
 git clone https://go.googlesource.com/website testdata
 git -C testdata tag example 8bb4a56901ae3b427039d490207a99b48245de2c
 git -C testdata checkout example
fi
#
# Build mdtool binary for testing purposes.
#
rm -f mdtool.exe
go build $BUILDARGS -o mdtool.exe .
#
# Run the tool on a set of input files from 'testdata'.
#
FILES=$(find testdata -name "*.md" -print)
N=$(echo $FILES | wc -w)
for F in $FILES
do
 ./mdtool.exe +x +a $F > /dev/null
done
echo "finished processing $N files, no crashes"
$

Вот пример запуска нашего теста:


$ /bin/sh integration_test.sh
...
finished processing 380 files, no crashes
$

Двоичный файл mdtool успешно обработал набор входных файлов… но какую часть исходного кода инструмента мы задействовали? Далее соберем профиль покрытия, чтобы узнать это.


Как использовать интеграционный тест для сбора данных о покрытии


Напишем сценарий-оболочку, вызывающий предыдущий сценарий и создающий инструмент для покрытия, а затем выполняет пост-процессинг результирующих профилей:


$ cat wrap_test_for_coverage.sh
#!/bin/sh
set -e
PKGARGS="$*"
#
# Setup
#
rm -rf covdatafiles
mkdir covdatafiles
#
# Pass in "-cover" to the script to build for coverage, then
# run with GOCOVERDIR set.
#
GOCOVERDIR=covdatafiles \
 /bin/sh integration_test.sh -cover $PKGARGS
#
# Post-process the resulting profiles.
#
go tool covdata percent -i=covdatafiles
$

Вот некоторые ключевые моменты, на которые следует обратить внимание в приведенной выше оболочке:


  • она запускается с флагом -cover, когда выполняется integration_test.sh, что дает нам бинарник покрытия mdtool.exe;
  • она устанавливает переменную среды GOCOVERDIR в значение пути в каталог записи файлов данных покрытия;
  • по завершении теста для создания отчета о проценте охваченных операторов запускается go tool covdata percent.

Вот результат запуска этой новой оболочки:


$ /bin/sh wrap_test_for_coverage.sh
...
 gitlab.com/golang-commonmark/mdtool coverage: 48.1% of statements
$
# Note: covdatafiles now contains 381 files.

Теперь у нас есть некоторое представление о том, насколько хорошо интеграционные тесты работают с исходниками.


Если мы внесем изменения для улучшения комплекта тестов, а затем выполним второй запуск сбора покрытия, то в отчете о покрытии увидим изменения. Предположим, например, что мы улучшаем тест новыми строками в файле integration_test.sh:


./mdtool.exe +ty testdata/README.md > /dev/null
./mdtool.exe +ta < testdata/README.md > /dev/null

Запуск оболочки:


$ /bin/sh wrap_test_for_coverage.sh
finished processing 380 files, no crashes
 gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$

Покрытие операторов увеличилось с 48% до 54%.


Выбор пакетов, которые будут покрываться


По умолчанию go build -cover использует только пакеты, которые являются частью создаваемого модуля Go, здесь это gitlab.com/golang-commonmark/mdtool. Однако иногда полезно расширить инструментарий покрытия на другие пакеты; это можно сделать, например, путем передачи флага -coverpkg в go build -cover.


В значительной степени mdtool — просто оболочка вокруг пакета gitlab.com/golang-commonmark/markdown, поэтому markdown интересно включить в набор инструментированных пакетов.


Вот файл go.mod для mdtool:


$ head go.mod
module gitlab.com/golang-commonmark/mdtool

go 1.17

require (
 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
 gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
)

Чтобы управлять тем, какие пакеты включаются в анализ покрытия, можно воспользоваться флагом -coverpkg:


$ /bin/sh wrap_test_for_coverage.sh -coverpkg=gitlab.com/golang-commonmark/markdown,gitlab.com/golang-commonmark/mdtool
...
 gitlab.com/golang-commonmark/markdown coverage: 70.6% of statements
 gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$

Работа с файлами данных покрытия


Когда интеграционный тест покрытия завершен и записан набор файлов необработанных данных (содержимое covdatafiles), эти файлы можно обработать разными способами.


Преобразование профилей в текстовый формат ‘-coverprofile’


При работе с модульными тестами можно выполнить go test -coverprofile=abc.txt — запись профиля для данного тестового покрытия в виде текста.


С помощью бинарников, собранных go build -cover, можно сгенерировать профиль в текстовом формате постфактум, запустив go tool covdata textfmt на файлах, отправленных в каталог GOCOVERDIR.


После завершения этого шага вы можете использовать go tool cover -func=<file> или go tool cover -html=<file>, чтобы интерпретировать / визуализировать данные так же, как с go test -coverprofile.


Пример:


$ /bin/sh wrap_test_for_coverage.sh
...
$ go tool covdata textfmt -i=covdatafiles -o=cov.txt
$ go tool cover -func=cov.txt
gitlab.com/golang-commonmark/mdtool/main.go:40: readFromStdin 100.0%
gitlab.com/golang-commonmark/mdtool/main.go:44: readFromFile 80.0%
gitlab.com/golang-commonmark/mdtool/main.go:54: readFromWeb 0.0%
gitlab.com/golang-commonmark/mdtool/main.go:64: readInput 80.0%
gitlab.com/golang-commonmark/mdtool/main.go:74: extractText 100.0%
gitlab.com/golang-commonmark/mdtool/main.go:88: writePreamble 100.0%
gitlab.com/golang-commonmark/mdtool/main.go:111: writePostamble 100.0%
gitlab.com/golang-commonmark/mdtool/main.go:118: handler 0.0%
gitlab.com/golang-commonmark/mdtool/main.go:139: main 51.6%
total: (statements) 54.6%
$

Слияние необработанных профилей командой go tool covdata merge


Каждое выполнение встроенного приложения с -cover будет записывать один или несколько файлов данных в каталог, указанный в переменной среды GOCOVERDIR. Если в ходе интеграционного теста выполняется N запусков программы, в конечном счете в выходном каталоге будет O(N) файлов. Обычно в файлах данных много дублированного содержимого, поэтому для сжатия данных и/или объединения наборов данных разных запусков интеграционных тестов вы можете использовать команду слияния профилей go tool covdata merge:


$ /bin/sh wrap_test_for_coverage.sh
finished processing 380 files, no crashes
 gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$ ls covdatafiles
covcounters.13326b42c2a107249da22f6e0d35b638.772307.1677775306041466651
covcounters.13326b42c2a107249da22f6e0d35b638.772314.1677775306053066987
...
covcounters.13326b42c2a107249da22f6e0d35b638.774973.1677775310032569308
covmeta.13326b42c2a107249da22f6e0d35b638
$ ls covdatafiles | wc
 381 381 27401
$ rm -rf merged ; mkdir merged ; go tool covdata merge -i=covdatafiles -o=merged
$ ls merged
covcounters.13326b42c2a107249da22f6e0d35b638.0.1677775331350024014
covmeta.13326b42c2a107249da22f6e0d35b638
$

Команда go tool covdata merge также принимает -pkg, который можно использовать для выбора конкретного пакета или набора пакетов.


Это полезно для объединения результатов различных типов тестовых запусков, включая запуски, созданные другими комплектами тестов.


Итоги


С выпуском версии 1.20 инструментарий покрытия кода Go перестал ограничиваться тестами пакетов, но поддерживает сбор профилей из более крупных интеграционных тестов. Мы надеемся, что вы воспользуетесь новыми функциями, чтобы понять, насколько хорошо работают крупные и сложные тесты, а также какие части вашего исходного кода они используют.


Попробуйте эти новые функции и, как всегда, если у вас возникнут проблемы, сообщите о них на GitHub. Спасибо.




Комментарии (0)