Ответ
Да, я писал E2E-тесты. Важно понимать, что E2E-тест проверяет всю систему в сборе как "черный ящик", в отличие от юнит- или интеграционных тестов.
Мой подход к E2E-тестированию выглядит так:
Подготовка окружения: Тест должен запускать реальные экземпляры зависимостей (база данных, кэш, другие сервисы). Лучший инструмент для этого в Go — библиотека
testcontainers-go
. Она позволяет программно поднимать нужные Docker-контейнеры на время выполнения тестов и автоматически их останавливать.Запуск приложения: В рамках теста мы компилируем и запускаем наше приложение как отдельный процесс или в горутине. Оно должно слушать реальный порт (например,
localhost:8080
).Выполнение тестовых сценариев: Тесты отправляют настоящие HTTP-запросы к запущенному приложению с помощью стандартного
http.Client
.Проверка результатов: Мы проверяем не только код ответа и тело, но и состояние системы (например, делаем прямой запрос в тестовую БД, чтобы убедиться, что запись действительно создалась).
Ключевые инструменты:
testing
: Стандартный пакет для организации тестов.testcontainers-go
: Для управления жизненным циклом Docker-контейнеров с зависимостями (Postgres, Redis и т.д.).net/http
: Стандартный HTTP-клиент для отправки запросов к нашему сервису.testify/assert
иtestify/require
: Для удобных и читаемых проверок (ассертов).
Пример концептуальной структуры теста:
// Этот код показывает структуру, а не полную реализацию
func TestUserFlow_E2E(t *testing.T) {
// 1. Запускаем контейнер с PostgreSQL с помощью testcontainers-go
ctx := context.Background()
dbContainer, err := postgres.RunContainer(ctx, /* ...opts */)
require.NoError(t, err)
defer dbContainer.Terminate(ctx)
// Получаем connection string к тестовой БД
connStr, err := dbContainer.ConnectionString(ctx, "sslmode=disable")
require.NoError(t, err)
// 2. Запускаем наше приложение в горутине, передав ему connStr
app := NewApp(connStr)
go app.Run(":8081") // Используем другой порт, чтобы не конфликтовать
time.Sleep(1 * time.Second) // Даем время на запуск
// 3. Выполняем тестовый сценарий
// Создаем нового пользователя
resp, err := http.Post("http://localhost:8081/users", "application/json", bytes.NewBuffer(newUserJSON))
require.NoError(t, err)
assert.Equal(t, http.StatusCreated, resp.StatusCode)
// 4. Проверяем, что пользователь действительно появился в БД
// ... делаем прямой SQL-запрос в тестовую БД ...
}
Этот подход, в отличие от моков или httptest
, дает максимальную уверенность в том, что все компоненты системы корректно работают вместе.