RSS Telegram YouTube Apple Яндекс Spotify Google Amazon Почта

18. testing-фреймворк в Go

23.09.2023

Скачать

К списку выпусков

Основы testing пакета

Я люблю заглядывать в тесты, чтобы быстро понять, как работает код. Например, тесты в fmt-пакете объясняют работу ряда модификаторов нагляднее, чем документация.

Также я обращаю внимание на пакет *_test.go файла. Если этот тот же пакет, что и тестируемый, то это модульное тестирование (есть доступ к неэкспортированные переменным). Если же это другой пакет, то часто это интеграционное тестирование, либо "black box" тестирование, т.е. такое, в котором проверяется работа нескольких функций/пакетов вместе.

Также можно выполнение тестов пропускать, используя метод Skip(). Запуск тестов поддерживает переменную, позволяющую просигнализировать, что тесты должны быть короткими, для чего тесты следует запускать с флагом -short, а в тестах проверять значение этой переменной вызовом testing.Short().

TestMain

Если нужно выполнить код, до и после выполнения тестов, то удобно использовать TestMain функцию:

func TestMain(m *testing.M) {
    // setup
    os.Exit(m.Run())
    // teardown
}

Организацию по типу табличных тестов можно сделать и с использованием суб-тестов t.Run().

Примеры (Examples)

Удобны для демонстрации вашей идеи. Из полезного стоит отметить, что Examples-функции можно публиковать в Go playground'е (как и обычные тесты, но не бенчмарки), чтобы продемонстрировать работу вашего кода и сбросить например коллеге в чат ссылку на него.

Использование t.Error(), t.Errorf(), t.Fatal(), t.Fatalf() и т. д.

Прервать выполнение теста можно с помощью t.Fatal(f) и t.Error(f). Лучше использовать t.Error(f), чтобы выявить за один тест как можно больше ошибок и только если дальнеший тест невозможен из-за текущего состояния системы, то использовать t.Fatal(f).

Бенчмаркинг с testing пакетом и для чего это нужно?

По-умолчанию бэнчмарки не запускаются при выполнении go test. Нужно явно указать флаг -bench. Причем, если не указать название бенчмарка, то будут запущены все бенчмарки в пакете. Выполняются они последовательно.

Бенчмарк по итогу работы выводит на экран скорость выполнения тестируемого кода и сколько было мемори-аллокаций (дополнительный флаг -benchmem).

Анализ результатов бенчмаркинга.

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

Фаззи-тестирование

Появилось в Go 1.18.

Часто используется для тестирования парсеров и энкодеров/декодеров. Применяется для тестирования быстрого простого кода без разделяемого стейта (shared state).

В основе фаззи-тестов сравнение стак-трейсов. Генерируются значения, сравниваются отклонения от предыдущих стак-трейсов. Фаззи-тест никогда не может быть пройден, если самостоятельно его не ограничить.

Документация: https://go.dev/security/fuzz/

Подробный туториал: https://go.dev/doc/tutorial/fuzz

Где хранится кеш данных?

Механизм кеширования, введенный в Go 1.10, сильно ускорил выполнение тестов, исходный код которых не был изменен.

Вы точно обратили внимание как быстро выполняются тесты, особенно, если это второй и последующие запуски. Дело в том, что билды и результаты выполнения тестов кешируются в директории $GOCACHE или в домашней директории пользователя $HOME/.cache/go-build.

При выполнении тестов, если исходный код не изменялся, то тесты выполняются на основе кеша, о чем сообщается коментарием (cached) в консоли рядом с тестом.

Очистка кеша возможно командой go clean -cache.

fstest

Добавлено в Go 1.16 для мока файловой системы в тестах.

Часто приходится работать с файловой системой в коде. В этом случае в качестве зависимости лучше указать 'io/fs.FS' (интерфейс с единственным методом Open(), возвращает файл и ошибку).

Хорошая демонстрация использования: https://quii.gitbook.io/learn-go-with-tests/go-fundamentals/reading-files

Также здесь же есть удобная хелпер-функции TestFS(fsys fs.FS, expected ...string) для проверки наличия файлов в указанной файловой системе

iotest

Если в вашем коде есть функции/методы, которые принимаю Reader` или `Writer интерфейсы, то вы можете протестировать их с помощью iotest` пакета. Он содержит реализации `Reader` и `Writer интерфейсов, для некоторых корнер-кейсов: ErrReader`, `HalfReader`, `OneByteReader`, `TimeoutReader и т.д.

Параллельное тестирование

go test -p 1 - запускает тесты последовательно, тестируется один пакет за раз. В противном случае запускаются

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

Суб-тесты выполняются последовательно, поэтому если нужно их запустить параллельно, то нужно делать вызов t.Parallel() в каждом из них.

Покрытие кода

Покрытие кода тестами проверить очень легко - просто добавить флаг -cover` при запуске `go test. Мы увидим проценты покрытия по каждому протестированному пакету.

Можно пойти дальше, сформировать профайл покрытия кода тестами: go test -coverprofile=coverage.out ./... (на выходе простой файл с перечислением позиций в коде, покрытых тестом или нет) и после этого визуализировать его с помощью утилиты go tool cover -html=coverage.out.

Покрытие кода в первую очередь используем для идентификации непротестированных областей.

Если у вас большая команда, то часто покрытие кода тестами включают в CI-цикл.

К списку выпусков