Тестирование — важнейший аспект разработки программного обеспечения, особенно для веб‑приложений. В Go тестирование встроено в язык и предоставляет мощные инструменты для написания и выполнения тестов. В этой статье мы рассмотрим поток веб‑приложения на Go, как писать модульные тесты для каждого слоя приложения.
Поток веб-приложения Go
При разработке приложений на Go важно понимать, как работает поток данных и управления через каждый слой приложения. Типичный поток может выглядеть следующим образом:

На этой схеме запрос начинается в браузере и проходит через промежуточный слой, который затем передает его контроллеру. Контроллер взаимодействует с менеджером, который затем передает данные сервисному слою. Сервисный слой, в свою очередь, взаимодействует со слоем валидации для проверки входных данных и бизнес‑правил, а также с хранилищем для сохранения данных, утилитами и вспомогательными модулями для дополнительной функциональности. Наконец, хранилище взаимодействует с моделью, которая и обрабатывает полученный запрос.
Написание модульных тестов
Чтобы обеспечить качество и корректность работы каждого слоя нашего приложения, мы должны написать как модульные, так и интеграционные тесты. Юнит‑тесты должны быть направлены на тестирование отдельных единиц кода в изолированном режиме, например, функций или методов в рамках одного модуля. Написание модульных тестов для каждого слоя приложения поможет нам выявить проблемы на ранних этапах разработки и убедиться, что каждый юнит кода ведет себя правильно.
Например, чтобы написать модульные тесты для сервисного слоя, мы можем создать макетные реализации для слоя проверки, репозитория, утилит и вспомогательных модулей, а затем протестировать каждый метод сервисного слоя в изоляции. С помощью макетов мы можем управлять вводом и выводом каждого метода и убедиться, что сервисный слой ведет себя так, как ожидается.
Для написания модульных тестов мы можем использовать фреймворк тестирования, например встроенный в Go пакет тестирования или сторонний пакет, например testify. Эти фреймворки предоставляют инструменты для создания имитационных реализаций зависимостей, запуска тестов и генерации отчетов о покрытии.
Пример теста
Допустим, у нас есть сервисный слой, который обрабатывает аутентификацию пользователей, и у него есть следующий метод:
type AuthService interface { Authenticate(username string, password string) (bool, error) } type AuthServiceImpl struct { validator ValidationService repo UserRepository } func NewAuthServiceImpl(validator ValidationService, repo UserRepository) AuthService { return &AuthServiceImpl{ validator: validator, repo: repo, } } func (s *AuthServiceImpl) Authenticate(username string, password string) (bool, error) { if err := s.validator.ValidateUsername(username); err != nil { return false, err } if err := s.validator.ValidatePassword(password); err != nil { return false, err } return s.repo.Authenticate(username, password) } //Validation Service type ValidationService interface { ValidateUsername(username string) error ValidatePassword(password string) error } type ValidationServiceImpl struct{} func (svc *ValidationServiceImpl) ValidateUsername(username string) error { // perform validation logic return nil // return nil if validation succeeds, or an error if it fails } func (svc *ValidationServiceImpl) ValidatePassword(password string) error { // perform validation logic return nil // return nil if validation succeeds, or an error if it fails } type UserRepository interface { Authenticate(username string, password string) (bool, error) } type UserRepositoryImpl struct{} func (repo *UserRepositoryImpl) Authenticate(username string, password string) (bool, error) { // perform authentication logic return true, nil } type AuthService interface { Authenticate(username string, password string) (bool, error) } type AuthServiceImpl struct { validator ValidationService repo UserRepository } func NewAuthServiceImpl(validator ValidationService, repo UserRepository) AuthService { return &AuthServiceImpl{ validator: validator, repo: repo, } } func (s *AuthServiceImpl) Authenticate(username string, password string) (bool, error) { if err := s.validator.ValidateUsername(username); err != nil { return false, err } if err := s.validator.ValidatePassword(password); err != nil { return false, err } return s.repo.Authenticate(username, password) } //Validation Service type ValidationService interface { ValidateUsername(username string) error ValidatePassword(password string) error } type ValidationServiceImpl struct{} func (svc *ValidationServiceImpl) ValidateUsername(username string) error { // perform validation logic return nil // return nil if validation succeeds, or an error if it fails } func (svc *ValidationServiceImpl) ValidatePassword(password string) error { // perform validation logic return nil // return nil if validation succeeds, or an error if it fails } type UserRepository interface { Authenticate(username string, password string) (bool, error) } type UserRepositoryImpl struct{} func (repo *UserRepositoryImpl) Authenticate(username string, password string) (bool, error) { // perform authentication logic return true, nil }
Чтобы протестировать этот сервисный слой, мы можем создать макеты реализаций зависимостей ValidationService и UserRepository, например, с помощью библиотеки testity/mock.
type MockValidationService struct { mock.Mock } func (m *MockValidationService) ValidateUsername(username string) error { args := m.Called(username) return args.Error(0) } func (m *MockValidationService) ValidatePassword(password string) error { args := m.Called(password) return args.Error(0) } type MockUserRepository struct { mock.Mock } func (m *MockUserRepository) Authenticate(username string, password string) (bool, error) { args := m.Called(username, password) return args.Bool(0), args.Error(1) } func TestAuthenticate(t *testing.T) { username := "testuser" password := "testpassword" mockValidator := new(MockValidationService) mockValidator.On("ValidateUsername", username).Return(nil) mockValidator.On("ValidatePassword", password).Return(nil) mockRepo := new(MockUserRepository) mockRepo.On("Authenticate", username, password).Return(true, nil) authService := NewAuthServiceImpl(mockValidator, mockRepo) authenticated, err := authService.Authenticate(username, password) if err != nil { t.Errorf("Unexpected error: %v", err) } if !authenticated { t.Error("Expected authentication to succeed, but it failed") } }
В этом примере мы импортируем пакет mock из набора инструментов testify и создаем имитационные реализации зависимостей ValidationService и UserRepository с помощью структур MockValidationService и MockUserRepository. Затем мы используем метод On, чтобы указать ожидаемое поведение каждого вызова метода, и передаем макеты зависимостей в метод NewAuthServiceImpl для создания нового экземпляра AuthService.
Наконец, мы вызываем метод Authenticate с тестовыми данными и проверяем, что результат соответствует ожиданиям. Пакет mock позаботится о создании необходимых реализаций и проверке того, что они вызываются так, как ожидалось.
Заключение
Написание модульных тестов это важная часть разработки качественного веб‑приложения на Go. Тестируя каждый слой приложения в отдельности и как систему, мы можем выявить проблемы на ранних этапах разработки и убедиться, что приложение работает правильно.
Если вы работаете с Go и интересуетесь вопросами тестирования, приглашаем вас на открытые занятия курса «Автоматизированное тестирование веб‑сервисов на Go».
-
31 июля в 20:00 — открытый урок «Псевдосервер за 15 минут: учим SoapUI делать вид, что он API». Разберём, как с помощью SoapUI эмулировать поведение веб‑сервиса без запуска реального бэкенда.
-
12 августа в 20:00 — открытый урок «MITM: почему бесплатный VPN знает о вас больше, чем мама». Поговорим о перехвате трафика, анализе данных и связанных с этим рисках.
Также можно пройти вступительное тестирование, чтобы узнать, достаточно ли ваших текущих знаний для поступления на курс автоматизации тестирования на Go.
ссылка на оригинал статьи https://habr.com/ru/articles/930136/
Добавить комментарий