Тестирование в Go: Unit, Integration, E2E — полное руководство

Онлайн-тренажер Python для начинающих

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

Начать курс

Тестирование в Go: Unit, Integration, E2E — полное руководство

Тестирование — неотъемлемая часть разработки качественного программного обеспечения. В языке Go (Golang) тестирование заложено на уровне стандартной библиотеки, что делает его простым и эффективным. В этой статье мы разберем три основных уровня тестирования: модульные (unit), интеграционные (integration) и сквозные (e2e) тесты. Вы узнаете, как их писать, когда применять и какие инструменты использовать.

Почему тестирование в Go так важно?

Go был создан с акцентом на простоту и надежность. Встроенный пакет testing и инструмент go test позволяют легко добавлять тесты в любой проект. Команда Go придерживается философии: "Если код не протестирован, он не работает". Тестирование помогает:

  • Обнаруживать ошибки на ранних стадиях разработки
  • Упрощать рефакторинг и поддержку кода
  • Документировать поведение функций и модулей
  • Повышать уверенность в стабильности приложения

Unit-тестирование в Go

Модульные тесты (unit-тесты) проверяют работу отдельных функций, методов или структур изолированно от внешних зависимостей. В Go они пишутся в файлах с суффиксом _test.go.

Основы unit-тестов

Рассмотрим простой пример. Допустим, у нас есть функция сложения:

// math.go
package main

func Add(a, b int) int {
    return a + b
}

Тест для этой функции будет выглядеть так:

// math_test.go
package main

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5
    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

Запуск теста: go test -v

Табличные тесты (Table-driven tests)

Go поощряет использование табличных тестов для проверки множества сценариев:

func TestAddTableDriven(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -1, -2, -3},
        {"zero", 0, 0, 0},
        {"mixed", -5, 10, 5},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.want {
                t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}

Тестирование с моками (Mock)

Для изоляции unit-тестов часто требуются моки. В Go можно использовать интерфейсы и библиотеки, такие как gomock или testify/mock.

// Определяем интерфейс
type UserRepository interface {
    GetUser(id int) (User, error)
}

// Тест с использованием testify
func TestGetUserName(t *testing.T) {
    mockRepo := new(MockUserRepository)
    mockRepo.On("GetUser", 1).Return(User{Name: "Alice"}, nil)

    service := NewUserService(mockRepo)
    name, err := service.GetUserName(1)

    assert.NoError(t, err)
    assert.Equal(t, "Alice", name)
    mockRepo.AssertExpectations(t)
}

Интеграционное тестирование в Go

Интеграционные тесты проверяют взаимодействие между компонентами системы: базами данных, API, файловыми системами и другими внешними сервисами. В Go принято выносить их в отдельные пакеты или использовать build tags.

Тестирование с базой данных

Для тестирования работы с БД часто используют testcontainers или временные базы данных:

// integration_test.go (с build tag)
//go:build integration

package integration

import (
    "database/sql"
    "testing"
    _ "github.com/lib/pq"
)

func TestUserRepository(t *testing.T) {
    // Подключение к тестовой БД
    db, err := sql.Open("postgres", "host=localhost port=5432 user=test password=test dbname=testdb sslmode=disable")
    if err != nil {
        t.Fatalf("failed to connect: %v", err)
    }
    defer db.Close()

    repo := NewUserRepository(db)
    user, err := repo.CreateUser("test@example.com")
    if err != nil {
        t.Fatalf("failed to create user: %v", err)
    }

    if user.Email != "test@example.com" {
        t.Errorf("expected email test@example.com, got %s", user.Email)
    }
}

Запуск интеграционных тестов: go test -tags=integration -v ./...

Тестирование HTTP-сервера

Пакет net/http/httptest позволяет тестировать HTTP-обработчики без запуска реального сервера:

func TestHandler(t *testing.T) {
    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusOK)
        w.Write([]byte(`{"message": "hello"}`))
    })

    req := httptest.NewRequest("GET", "/api/hello", nil)
    w := httptest.NewRecorder()
    handler.ServeHTTP(w, req)

    resp := w.Result()
    body, _ := io.ReadAll(resp.Body)

    if resp.StatusCode != http.StatusOK {
        t.Errorf("expected status 200, got %d", resp.StatusCode)
    }

    expected := `{"message": "hello"}`
    if string(body) != expected {
        t.Errorf("expected body %s, got %s", expected, string(body))
    }
}

E2E-тестирование в Go

Сквозные тесты (end-to-end) проверяют работу приложения целиком, включая все внешние зависимости. Они имитируют действия реального пользователя и обычно запускаются в CI/CD после unit и integration тестов.

Пример E2E-теста для REST API

// e2e_test.go
//go:build e2e

package e2e

import (
    "net/http"
    "testing"
    "time"
)

func TestFullUserFlow(t *testing.T) {
    // Предполагаем, что сервер уже запущен
    baseURL := "http://localhost:8080"
    client := &http.Client{Timeout: 10 * time.Second}

    // 1. Создание пользователя
    resp, err := client.Post(baseURL+"/api/users", "application/json", 
        strings.NewReader(`{"email":"test@test.com","name":"Test User"}`))
    if err != nil {                

Блоги

Рекомендаций по книгам