Оглавление


1. Введение в тестирование ПО

  • Тестирование — процесс проверки программного обеспечения на наличие ошибок.
  • Главная цель тестирования — обеспечение надёжности и качества кода.
  • Тестирование — не гарантирует полного отсутствия ошибок, но снижает риски их появления в production.

2. Зачем нужны тесты

  • Надёжность: Дают уверенность, что новый функционал не ломает уже реализованный.
  • Чистота архитектуры: Способствуют написанию слабо связанного, тестируемого и поддерживаемого кода.
  • Безопасный рефакторинг: Позволяют менять код, будучи уверенным в сохранении его поведения.
  • Живая документация: Тесты показывают, как должен работать код и использоваться интерфейсы.

3. Основные определения

  • Тестирование: процесс поиска ошибок в ПО.
  • Тестирование находит ошибки, но не доказывает их отсутствие.

4. Классификация тестов

Тесты классифицируются по нескольким критериям:

По цели:

  • Функциональное: Проверяет ЧТО делает система (соответствие требованиям).
  • Нефункциональное: Проверяет КАК система работает (производительность, безопасность, надёжность).

По методу доступа к коду:

  • Чёрный ящик (Black box): Без знания о внутреннем устройстве.
  • Белый ящик (White box): С полным знанием исходного кода.
  • Серый ящик (Grey box): Частичное знание устройства системы.

По степени автоматизации:

  • Ручное
  • Автоматическое

По уровню:

  • Модульное (Unit): Изолированное тестирование отдельных функций/методов.
  • Интеграционное: Проверка взаимодействия между компонентами.
  • Сквозное (End-to-End, e2e): Тестирование полной пользовательской цепочки.

5. Пирамида тестирования

Модель для балансировки типов тестов по затратам и эффективности:

  • Основание (Unit-тесты):
    • Самый многочисленный уровень.
    • Быстрые, дешёвые, изолированные.
    • Работают по принципу “белого ящика”.
  • Середина (Интеграционные):
    • Меньше по численности, чем Unit.
    • Проверяют взаимодействие модулей.
    • Медленнее, требуют дополнительной инфраструктуры.
  • Вершина (End-to-End):
    • Самые редкие, дорогие и медленные.
    • Проверяют систему с точки зрения пользователя (“чёрный ящик”).

Фундаментальная концепция: большинство тестов в проекте — это Unit-тесты.


6. Модульное (Unit) тестирование в Go

  • Модульные тесты в Go предназначены для изолированного тестирования отдельных компонентов.
  • Упор на чеклист фундаментальных принципов: изоляция, независимость, повторяемость.

7. Паттерн AAA (Arrange, Act, Assert)

Классическая структура для написания читаемых тестов:

  1. Arrange (Подготовка):
    • Инициализация всех нужных данных и окружения.
  2. Act (Действие):
    • Вызов тестируемой функции или метода.
  3. Assert (Проверка):
    • Сравнение полученного результата с ожидаемым.

Этот паттерн помогает поддерживать логику и повторяемость тестов.


8. Основные правила и соглашения тестирования в Go

  • Все тесты должны быть помещены в файлы с суффиксом _test.go.
  • Каждая тестовая функция:
    • Начинается с префикса Test
    • Принимает аргумент t *testing.T
    • Не возвращает значений

9. Использование моков (Test Doubles)

  • Моки (Mocks) — разновидность тестовых двойников (Test Doubles).
  • Тестовый двойник: любой объект, имитирующий реальный компонент с целью изоляции тестируемого кода.
  • Назначение моков:
    • Имитация зависимостей.
    • Фокус на бизнес-логике без обращения к внешним системам.
  • Создание моков:
    • Ручное (для простых интерфейсов)
    • Автоматическое (генераторы моков, например, GoMock)

10. GoMock: основные возможности

  • GoMock — инструмент для гибкой настройки моков в тестах Go.
  • Ключевые методы и возможности:
    • mock.EXPECT(): начало сценария ожидания вызова.
    • MethodName(...): указание ожидаемого метода.
    • gomock.Any(), gomock.Eq(): настройка ожидаемых аргументов.
    • .Return(...): определение возвращаемых значений.
    • .Times(1), .AnyTimes(): сколько раз ожидается вызов метода.
    • gomock.InOrder(...): задание строгой последовательности вызовов.

11. Лучшие практики использования моков

  • Мокайте только внешние зависимости.
    • Не используйте моки там, где можно протестировать реальным объектом.
  • Простота моков:
    • Моки должны содержать минимальное количество логики, чтобы не стать источником багов.
  • Выбирайте стратегию проверки:
    • Моки нужны в основном для проверки вызовов (что сервис A вызывает метод сервиса B).
    • В большинстве случаев лучше проверять конечное состояние системы, а не факт вызова.

12. Популярные библиотеки для тестирования

Testify

  • Де-факто стандарт для тестирования в Go.
  • Функции:
    • Утверждения (Assertions): удобные методы для проверки (assert, require).
      • assert: фиксация ошибки, продолжение выполнения.
      • require: при ошибке тест завершает выполнение немедленно.
    • Встроенные моки для тестирования зависимостей.
    • Тестовые наборы (Suites): групировка тестов и методы жизненного цикла (SetupTest, TearDownTest и т.д.)

ozon/allure-go

  • Генерирует наглядные HTML-отчеты в формате Allure.
  • Преимущества:
    • Яркие отчеты по шагам, статусу, времени выполнения.
    • Группировка тестов и хуки Before/After для подготовки/завершения.
    • Шаги (Steps): явное логическое деление теста.
    • Метки (Labels): поддержка метаданных Allure для фильтрации и классификации.

13. Итоги и ключевые принципы

  • Проблемы тестирования: нестабильные тесты, сложность окружения, скрытые зависимости.
  • Принцип инверсии зависимостей (DIP): код должен зависеть от абстракций (интерфейсов), это делает код модульным и тестируемым.
  • Интеграционные тесты: важны для проверки взаимодействий, но требуют дополнительных усилий.
  • Моки: позволяют изолировать бизнес-логику от внешних зависимостей и упростить модульное тестирование.
  • Библиотеки: Testify — удобные проверки и моки, allure-go — визуализация результатов.
  • Главная мысль: постоянное совершенствование подходов к тестированию — ключевой путь к созданию надёжного production-кода.

Фундаментальные концепции, которые должны повторяться:

  • Unit-тесты — основа качественного тестирования, их должно быть больше всего.
  • Тестирование не гарантирует полное отсутствие ошибок, но критично для обнаружения проблем.
  • Инверсии зависимостей и использование интерфейсов — фундаментальный путь к тестируемости и модульности кода.