Поддержка профилирования покрытия для интеграционных тестов

Содержание:

Обзор
Сборка двоичного файла для профилирования покрытия
Запуск двоичного файла с профилированием покрытия
Работа с файлами данных покрытия
Часто задаваемые вопросы
Ресурсы
Глоссарий

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

Обзор

Go предоставляет удобную поддержку сбора профилей покрытия на уровне модульных тестов с помощью команды “go test -coverprofile=... <pkg_target>”. Начиная с Go 1.20, пользователи теперь могут собирать профили покрытия для более крупных интеграционных тестов: более ресурсоемких и сложных тестов, которые выполняют несколько запусков заданного двоичного файла приложения.

Для модульных тестов сбор профиля покрытия и генерация отчета требуют два шага: запуск go test -coverprofile=..., за которым следует вызов go tool cover {-func,-html} для генерации отчета.

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

Сборка двоичного файла для профилирования покрытия

Чтобы собрать приложение для сбора профилей покрытия, передайте флаг -cover при вызове go build для целевого двоичного файла приложения. См. раздел ниже для примера вызова go build -cover. Полученный двоичный файл затем можно запустить с использованием переменной окружения для захвата профилей покрытия (см. следующий раздел о запуске).

Как выбираются пакеты для инструментирования

Во время вызова “go build -cover”, команда Go выберет пакеты в основном модуле для профилирования покрытия; другие пакеты, участвующие в сборке (зависимости, указанные в go.mod, или пакеты из стандартной библиотеки Go), по умолчанию не будут включены.

Например, вот игрушечная программа, содержащая главный пакет, локальный пакет модуля greetings и набор пакетов, импортированных извне модуля, включая (среди прочего) rsc.io/quote и fmt (ссылка на полную программу).

<code>$ cat go.mod
module mydomain.com

go 1.20

require rsc.io/quote v1.5.2

require (
  golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
  rsc.io/sampler v1.3.0 // indirect
)

$ cat myprogram.go
package main

import (
  "fmt"
  "mydomain.com/greetings"
  "rsc.io/quote"
)

func main() {
  fmt.Printf("I say %q and %q\n", quote.Hello(), greetings.Goodbye())
}
$ cat greetings/greetings.go
package greetings

func Goodbye() string {
  return "see ya"
}
$ go build -cover -o myprogram.exe .
$
</code>

Если собрать эту программу с флагом командной строки “-cover” и запустить, то в профиль будут включены ровно два пакета: main и mydomain.com/greetings; другие зависимые пакеты будут исключены.

Пользователям, которые хотят иметь больше контроля над тем, какие пакеты включаются для покрытия, можно собирать программы с флагом “-coverpkg”. Пример:

<code>$ go build -cover -o myprogramMorePkgs.exe -coverpkg=io,mydomain.com,rsc.io/quote .
$
</code>

В приведённой выше сборке пакет из mydomain.com и пакеты rsc.io/quote и io выбраны для профилирования; поскольку mydomain.com/greetings не указан явно, он будет исключён из профиля, несмотря на то, что находится в основном модуле.

Запуск двоичного файла с инструментированным покрытием

Двоичные файлы, собранные с флагом “-cover”, записывают данные профиля в конце выполнения в директорию, указанную через переменную окружения GOCOVERDIR. Пример:

<code>$ go build -cover -o myprogram.exe myprogram.go
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
I say "Hello, world." and "see ya"
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$
</code>

Обратите внимание на два файла, записанных в директорию somedata: эти (бинарные) файлы содержат результаты покрытия. Подробнее о том, как получить человекочитаемые результаты из этих файлов данных, см. в разделе о отчётах.

Если переменная окружения GOCOVERDIR не установлена, двоичный файл с инструментированным покрытием всё равно будет выполняться корректно, но выведет предупреждение. Пример:

<code>$ ./myprogram.exe
warning: GOCOVERDIR not set, no coverage data emitted
I say "Hello, world." and "see ya"
$
</code>

Тесты, включающие несколько запусков

Интеграционные тесты могут включать в себя несколько запусков программы; когда программа собирается с флагом “-cover”, каждый запуск создаёт новый файл данных. Пример

<code>$ mkdir somedata2
$ GOCOVERDIR=somedata2 ./myprogram.exe          // первый запуск
I say "Hello, world." and "see ya"
$ GOCOVERDIR=somedata2 ./myprogram.exe -flag    // второй запуск
I say "Hello, world." and "see ya"
$ ls somedata2
covcounters.890814fca98ac3a4d41b9bd2a7ec9f7f.2456041.1670259309405583534
covcounters.890814fca98ac3a4d41b9bd2a7ec9f7f.2456047.1670259309410891043
covmeta.890814fca98ac3a4d41b9bd2a7ec9f7f
$
</code>

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

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

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

Go 1.20 вводит новый инструмент 'covdata', который может использоваться для чтения и манипуляции файлами данных покрытия из директории GOCOVERDIR.

Инструмент covdata Go работает в различных режимах. Общий вид вызова инструмента covdata выглядит следующим образом

<code>$ go tool covdata <mode> -i=<dir1,dir2,...> ...flags...
</code>

где флаг “-i” предоставляет список директорий для чтения, каждая из которых получена при выполнении двоичного файла с инструментированным покрытием (через GOCOVERDIR).

Создание отчётов профиля покрытия

В этом разделе рассматривается использование “go tool covdata” для создания человекочитаемых отчётов из файлов данных покрытия.

Отчёт о проценте покрытых инструкций

Для получения метрики “процент покрытых инструкций” для каждого инструментированного пакета используйте команду “go tool covdata percent -i=<directory>”. Используя пример из раздела запуска

Проценты "покрытых инструкций" здесь соответствуют тем, которые сообщаются командой go test -cover.

Преобразование в устаревший текстовый формат

Вы можете преобразовать двоичные файлы данных покрытия в устаревший текстовый формат, создаваемый командой “go test -coverprofile=<outfile>”, используя селектор textfmt в go tool covdata. Полученный текстовый файл затем можно использовать с командами “go tool cover -func” или “go tool cover -html” для создания дополнительных отчетов. Пример:

<code>$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata textfmt -i=somedata -o profile.txt
$ cat profile.txt
mode: set
mydomain.com/myprogram.go:10.13,12.2 1 1
mydomain.com/greetings/greetings.go:3.23,5.2 1 1
$ go tool cover -func=profile.txt
mydomain.com/greetings/greetings.go:3:  Goodbye     100.0%
mydomain.com/myprogram.go:10:       main        100.0%
total:                  (statements)    100.0%
$
</code>

Объединение

Подкоманда merge в “go tool covdata” может использоваться для объединения профилей из нескольких каталогов данных.

Например, рассмотрим программу, которая запускается как на macOS, так и на Windows. Автор этой программы может захотеть объединить профили покрытия из отдельных запусков на каждой операционной системе в один профильный корпус, чтобы получить сводку покрытия кросс-платформенно. Пример:

<code>$ ls windows_datadir
covcounters.f3833f80c91d8229544b25a855285890.1025623.1667481441036838252
covcounters.f3833f80c91d8229544b25a855285890.1025628.1667481441042785007
covmeta.f3833f80c91d8229544b25a855285890
$ ls macos_datadir
covcounters.b245ad845b5068d116a4e25033b429fb.1025358.1667481440551734165
covcounters.b245ad845b5068d116a4e25033b429fb.1025364.1667481440557770197
covmeta.b245ad845b5068d116a4e25033b429fb
$ ls macos_datadir
$ mkdir merged
$ go tool covdata merge -i=windows_datadir,macos_datadir -o merged
$
</code>

Операция объединения выше объединит данные из указанных входных каталогов и запишет новый набор объединенных файлов данных в каталог “merged”.

Выбор пакетов

Большинство команд “go tool covdata” поддерживают флаг “-pkg” для выполнения выбора пакетов как часть операции; аргумент флага “-pkg” принимает тот же формат, что и флаг “-coverpkg” команды Go. Пример:

<code>
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata percent -i=somedata -pkg=mydomain.com/greetings
mydomain.com/greetings  coverage: 100.0% of statements
$ go tool covdata percent -i=somedata -pkg=nonexistentpackage
$
</code>

Флаг “-pkg” можно использовать для выбора конкретного подмножества интересующих пакетов для данного отчета.

Часто задаваемые вопросы

  1. Как можно запросить инструментирование покрытия для всех импортированных пакетов, упомянутых в файле go.mod
  2. Можно ли использовать go build -cover в режиме GOPATH/GO111MODULE=off?
  3. Если моя программа вызывает панику, будут ли записаны данные покрытия?
  4. Выберет ли -coverpkg=main мой главный пакет для профилирования?

Как можно запросить инструментирование покрытия для всех импортированных пакетов, упомянутых в файле go.mod

По умолчанию go build -cover будет инструментировать все пакеты основного модуля для покрытия, но не будет инструментировать импорты вне основного модуля (например, пакеты стандартной библиотеки или импорты, перечисленные в go.mod). Один из способов запросить инструментирование всех зависимостей, кроме стандартной библиотеки, — это передать вывод go list в -coverpkg. Вот пример, снова использующий примерную программу, упомянутую выше:

<code>$ go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' -deps . | paste -sd "," > pkgs.txt
$ go build -o myprogram.exe -coverpkg=`cat pkgs.txt` .
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
$ go tool covdata percent -i=somedata
golang.org/x/text/internal/tag  coverage: 78.4% of statements
golang.org/x/text/language  coverage: 35.5% of statements
mydomain.com    coverage: 100.0% of statements
mydomain.com/greetings  coverage: 100.0% of statements
rsc.io/quote    coverage: 25.0% of statements
rsc.io/sampler  coverage: 86.7% of statements
$
</code>

Можно ли использовать go build -cover в режиме GO111MODULE=off?

Yes, go build -cover работает с GO111MODULE=off. При сборке программы в режиме GO111MODULE=off только пакет, специально указанный в качестве цели в командной строке, будет инструментирован для профилирования. Используйте флаг -coverpkg, чтобы включить дополнительные пакеты в профиль.

Если моя программа паникует, будут ли данные о покрытии записаны?

Программы, собранные с помощью go build -cover, будут записывать полные данные профиля только в конце выполнения, если программа вызывает os.Exit() или возвращает значение из main.main нормально. Если программа завершается неперехваченной паникой или сталкивается с фатальным исключением (например, нарушением доступа к памяти, делением на ноль и т. д.), данные профиля из инструкций, выполненных во время работы, будут потеряны.

Будет ли -coverpkg=main выбирать мой главный пакет для профилирования?

Флаг -coverpkg принимает список путей импорта, а не список имён пакетов. Если вы хотите выбрать ваш main пакет для инструментирования покрытия, пожалуйста, укажите его по пути импорта, а не по имени. Пример (используя этот пример программы):

<code>$ go list -m
mydomain.com
$ go build -coverpkg=main -o oops.exe .
warning: no packages being built depend on matches for pattern main
$ go build -coverpkg=mydomain.com -o myprogram.exe .
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
I say "Hello, world." and "see ya"
$ go tool covdata percent -i=somedata
mydomain.com    coverage: 100.0% of statements
$
</code>

Ресурсы

  • Блог-пост, представляющий покрытие unit-тестов в Go 1.2:
    • Профилирование покрытия для unit-тестов было представлено как часть выпуска Go 1.2; смотрите этот блог-пост для подробностей.
  • Документация:
    • Документация пакета cmd/go описывает флаги сборки и тестирования, связанные с покрытием.
  • Технические детали:

Глоссарий

unit-тест: Тесты внутри файла *_test.go, связанные с конкретным Go пакетом, использующие Go пакет testing.

интеграционный тест: Более комплексный, тяжеловесный тест для заданного приложения или бинарного файла. Интеграционные тесты обычно включают в себя сборку программы или набора программ, а затем выполнение серии запусков программ с использованием множества входных данных и сценариев под контролем тестовой среды, которая может быть основана на Go пакете testing или нет.

GoRu.dev Golang на русском

На сайте представлена адаптированная под русский язык документация языка программирования Golang