Тестирование Go с нуля: гид для начинающего разработчика

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

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

Начать курс

Почему тестирование в Go — это не страшно, а необходимо

Когда вы впервые пишете на Go, кажется, что компилятор ловит почти всё: типы строгие, ошибки явные, nil под контролем. Но стоит вашему проекту вырасти до 500+ строк, как вы понимаете: без тестов жить нельзя. Одна правка в одном месте ломает другое, а вы узнаёте об этом только в продакшне.

В этой статье я покажу, с чего начать тестирование на Go начинающему разработчику. Мы разберёмся с юнит-тестами, табличными тестами, моками и покрытием кода. Никакой воды — только рабочие примеры и практические советы.

Если вы пишете на Go хотя бы пару месяцев, этот материал сэкономит вам часы отладки.

Что нужно знать перед стартом

Go предоставляет встроенный пакет testing — ничего дополнительно ставить не нужно. Тесты пишутся в файлах с суффиксом _test.go и запускаются командой:

go test ./...

Основные правила:

  • Функция теста должна называться TestXxx(t *testing.T), где Xxx — имя тестируемой функции с большой буквы.
  • Файлы тестов лежат рядом с тестируемым кодом.
  • Ошибки сообщаются через t.Error() или t.Fatal().

Давайте сразу перейдём к практике.

Пишем первый юнит-тест на Go

Допустим, у нас есть простая функция, которая суммирует два числа:

// calc.go
package calc

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

Создаём файл calc_test.go в той же папке:

// calc_test.go
package calc

import "testing"

func TestSum(t *testing.T) {
    result := Sum(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Sum(2,3) = %d; want %d", result, expected)
    }
}

Запускаем тест:

go test -v

Вы увидите зелёный PASS — ваша первая тестовая победа. Обратите внимание: мы используем t.Errorf (не фатальный) — тест продолжит работу, даже если один кейс упал.

Табличные тесты: когда один тест неудобен

Представьте, что нужно проверить функцию с десятком разных входных данных. Писать 10 отдельных тестов? Нет, в Go для этого есть табличные тесты (table-driven tests). Это стандарт для Go-сообщества.

Добавим в наш calc.go функцию деления:

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

Теперь напишем табличный тест:

func TestDivide(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
        wantErr  bool
    }{
        {"positive", 10, 2, 5, false},
        {"negative", -6, 3, -2, false},
        {"zero division", 5, 0, 0, true},
        {"same numbers", 7, 7, 1, false},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := Divide(tt.a, tt.b)
            if tt.wantErr {
                if err == nil {
                    t.Error("expected error, got nil")
                }
                return
            }
            if err != nil {
                t.Errorf("unexpected error: %v", err)
            }
            if got != tt.expected {
                t.Errorf("Divide(%d,%d) = %d; want %d", tt.a, tt.b, got, tt.expected)
            }
        })
    }
}

Что здесь крутого:

  • Каждый кейс — отдельная подтест с именем. Если один упадёт, остальные выполнятся.
  • Легко добавлять новые случаи — просто дописываете строку в структуру.
  • Код читается как спецификация.

Запустите с флагом -v — увидите имена всех подтестов. Это очень удобно при отладке.

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

Веб-приложения на Go — классика. Давайте протестируем простой HTTP-хендлер без запуска реального сервера. Go позволяет создать тестовый HTTP-запрос через httptest.NewRecorder.

Напишем обработчик, который возвращает JSON с приветствием:

// handler.go
package handler

import (
    "encoding/json"
    "net/http"
)

type Greeting struct {
    Message string `json:"message"`
}

func HelloHandler(w http.ResponseWriter, r *http.Request) {
    name := r.URL.Query().Get("name")
    if name == "" {
        name = "World"
    }
    resp := Greeting{Message: "Hello, " + name + "!"}
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(resp)
}

Тест:

// handler_test.go
package handler

import (
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestHelloHandler(t *testing.T) {
    req := httptest.NewRequest("GET", "/hello?name=Go", nil)
    w := httptest.NewRecorder()

    HelloHandler(w, req)

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

    var greeting Greeting
    json.NewDecoder(resp.Body).Decode(&greeting)

    expected := "Hello, Go!"
    if greeting.Message != expected {
        t.Errorf("expected %q, got %q", expected, greeting.Message)
    }
}

Преимущество: тест работает мгновенно, не нужно поднимать порты. Можно проверить статус, заголовки, тело ответа.

Моки (mocks) в Go: когда тестировать нужно с внешними зависимостями

Ваш код почти наверняка обращается к базе данных, API или файловой системе. В тестах мы не хотим зависеть от реальных сервисов — они медленные, ненадёжные и требуют настройки. Здесь помогают моки.

Лучший способ в Go — использовать интерфейсы. Если функция принимает интерфейс, вы можете подставить заглушку в тесте.

Пример: сохраняем пользователя в базу.

// user.go
type User struct {
    ID   int
    Name string
}

type UserRepository interface {
    Save(user User) error
}

func CreateUser(repo UserRepository, name string) (User, error) {
    user := User{Name: name}
    err := repo.Save(user)
    return user, err
}

В тесте создаём простой мок:

// user_test.go
type mockRepo struct {
    savedUser User
    shouldFail bool
}

func (m *mockRepo) Save(user User) error {
    if m.shouldFail {
        return errors.New("db error")
    }
    m.savedUser = user
    return nil
}

func TestCreateUser(t *testing.T                

Блоги

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