Тестирование #
Тесты - кусочки кода, проверяющие корректную функциональность вашего приложения, или его частей. Один такой кусочек называется тест кейсом, процесс их написания называется покрытием кода приложения тестами.
Тесты бывают различных видов, в зависимости от того, что они тестируют. Рассмотрим основные:
- Юнит тесты тестируют небольшие части кода, методы или классы. Типичный пример юнит теста - создать объект, вызвать его метод, проверить результат.
- Интеграционные тесты проверяют, что ваше приложение работает правильно в контексте внешних инструментов и сервисов, например, баз данных. Пример интеграционного теста - вызов метода REST API вашего приложения, проверка корректности результата.
- E2E (end to end) тесты тестируют приложение в целом, затрагивая максимальное количество его частей. Пример - на веб-интерфейсе приложения есть форма, при заполнении и отправки которой в базе появляется новая запись. E2E тест может сэмулировать действия на веб-интерфейсе, после чего проверить состояние базы данных.
Кроме видов тестов, важно понимать некоторые смежные с тестированием идеи:
- Mocking - техника эмуляции поведения какого-то метода или внешнего сервиса. Например, если метод возвращает какое-то постоянно меняющееся значение (биржевую котировку, например), то в целях предсказуемого теста можно создать мок объект, возвращающий заранее известную котировку, провести с ней некоторые вычисления и проверить результат на корректность.
- Edge case (пограничный случай) - маловероятный, но тем не менее возможный пример входных данных, который может получить тестируемый код. Пример - длина email при регистрации пользователя. Маловероятная, но максимально возможная длина email адреса - 320 символов, и тестируя пограничные случае мы можем быть уверены, что приложение корректно их обрабатывает.
- Тестовые данные - если код работает с данными, то для его тестирования нужны тестовые данные. Такие данные редко являются данными реальных пользователей (это было бы небезопасно), а их целью зачастую является воссоздание различных пограничных случаев.
- Тестовое окружение - во время тестов редко используется реальные база данных и внешние сервисы, это создавало бы ненужную активность (например, вставку тестовых данных в базу). Поэтому, частая практика - создать отдельное тестовое окружение, которое рождается перед стартом тестов и уничтожается после.
- Testcontainer, позволяет запускать необходимые для тест кейсов инструменты (базы данных, очереди) в Docker контейнерах.
В работе с тестами, как и везде, есть плюсы и минусы.
Плюсы:
- Написание тестов помогает формализовать работу приложения
- Для написания качественных тестов покрываемый ими код должен быть спроектирован на наобходимом уровне. Необходимость тестов может стать поводом для рефакторинга, улучшающего качество кода
- Тесты являются ещё одной формой документации, хочешь понять в деталях что делает класс - прочитай юнит тесты к нему
- Тесты помогают поддерживать работоспособность кода при его рефакторинге и добавления нового функционала. В случае некорректных изменений часть тестов сломается
- Тесты помогают поддерживать здоровый уровень качества кода приложения, особенно в больших командах, где люди приходят и уходят
Минусы:
- Дополнительная работа. Написание тестов и их поддержка требуют времени
- Плохо написанные тесты создают больше проблем, чем приносят пользы
Практически любой устоявшийся проект содержит тесты, и разработчикам необходимо уметь с ними работать. Уровень проникровения тестов в проект, их глубина и количество зависят от конкретных условий и варьируются от поверхностного тестирования до полного TDD.
Тестирование в Go #
В Go тестирование встроено в инструментарий (go CLI), и не требует внешних фреймворков. Основной инструмент - команда go test.
Правила к тестам:
- Файлы с тестами должны называться
*_test.go. -Тестовые функции -func TestFoo(t *testing.T), где Foo начинается с заглавной буквы. - Для конкурентных тестов используйте
t.Parallel()- но только если тест изолирован.
Базовые команды:
go test- Запустить все тесты в текущем пакете go testgo test -v- Запустить с выводом пройденных тестов (-v = verbose)go test ./...- Запустить тесты во всех подпакетах рекурсивноgo test -run Foo- Запустить конкретный тест (по имени)go test -cover- Запустить с измерением покрытия (в процентах)
Unit тестирование #
Для написания юнит тестов студент должен уметь писать качественные тест кейсы и пользоваться одним из популярных фреймворков для юнит тестирования (самый популярный - testify). Важнее всего понять идеи качественных кейсов, применять эти идеи к конкретной библиотеке - дело практики.
Основные рекоммендации для написания качественных юнит тестов:
- Проверяйте пограничные случаи
- Тестируйте не только успешные сценарии работы кода, но и неудачные - покрывайте тестами случаи, когда метод должен вернуть ошибку или кинуть exception
- Давайте тест кейсам короткие и понятные имена, описывающие суть их проверки. Если имя слишком длинное или непонятное, возможно, тест слишком общий или сложный
Популярные библиотеки и практики:
github.com/stretchr/testify- упрощает assertions (require, assert) и моки (mock.Mock).golang/mock- генерация mock-реализаций интерфейсов.go-sqlmock- мокинг *sql.DB / *sqlx.DB.go test -cover,go tool cover— измерение покрытия.
Избранные курсы и учебные ресурсы #
- Генерация и использование моков в Go
- Статья об общих подходах в unit тестах
- Изучения Go с помощью тестов
Интеграционные тесты #
Интеграционные тесты проверяют взаимодействия между частями системы (например - между слоем сервисов и слоем хранения данных) и взаимодействия с внешними сервисами. Для написания таких тестов нужно знать и уметь пользоваться следующими идеями и инструментами:
- testify — для удобных assert’ов и моков (require, assert, mock)
- Тестовые данные, независимые от основных данных приложения. Например - временные инстансы PostgreSQL создаваемая для прогона тестов, и уничтожаемая после
- Testcontainer - библиотека для запуска экземпляров нужных для тестов инструментов внутри Docker контейнеров
Примеры интеграционного тестирования в контексте бэкенд приложений:
- Взимодействия класса-сервиса и БД, куда сервис вставляет данные. Можно проверить реакцию БД на вставку некорректных данных и реакцию сервиса на возникшую при этом ошибку
- Взаимодействия класса-сервиса и внешнего REST API. Проверки парсинга ответов, реакции на HTTP ошибки (коды 4xx, 5xx) и сетевые ошибки (таймаут подключения)
- Взаимодействия REST контроллера и сервисов. Проверка того, что REST API вернёт ожидаемые ответы на различные наборы параметров в запросах. Реакции на ошибки - неправильно сформированные запросы, отсутствие необходимой авторизации