Зарегистрируйтесь, чтобы продолжить обучение

Заключение Go: Автоматическое тестирование

В этом курсе вы шаг за шагом познакомились с тестированием в Go. Мы начали с самых основ — простых unit-тестов, проверки ошибок и паник, а затем постепенно углублялись: разбирали табличные тесты, вспомогательные функции и интерфейс testing.TB, работу с временными файлами и каталогами, создание моков через интерфейсы и библиотеку testify, организацию параллельных тестов и защиту от гонок, измерение покрытия кода. В финале мы вышли на лучшие практики, которые помогают писать не только работающие, но и понятные, поддерживаемые тесты.

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

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

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

Теперь у вас есть всё необходимое, чтобы писать тесты в Go профессионально: от самых базовых проверок до сложных сценариев с моками, параллельностью и анализом покрытия. Дальше всё зависит от практики. Чем больше вы будете писать тестов в реальных проектах, тем увереннее станете применять приёмы, которые мы рассмотрели, и тем надёжнее станет ваш код.


Самостоятельная работа

Завершаем блок практикой по testify: быстро отлавливаем ошибки и проверяем детали.

Что сделать

  • Реализуйте Div(a, b int) (int, error), которая падает с ошибкой при делении на ноль.

    Пример реализации
    package calc
    
    import "errors"
    
    var ErrZeroDivisor = errors.New("division by zero")
    
    func Div(a, b int) (int, error) {
        if b == 0 {
            return 0, ErrZeroDivisor
        }
        return a / b, nil
    }
    
  • Напишите тесты с require (останавливает тест при ошибке) и assert (сравнение значений).

Что покрыть в тестах

  • b == 0: через require.Error и assert.Equal для текста/значения ошибки.
  • Несколько корректных кейсов деления: положительные, отрицательные, нули в делимом.
Показать решение
package calc

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestDiv_ErrorOnZero(t *testing.T) {
    _, err := Div(10, 0)
    require.Error(t, err)
    assert.Equal(t, ErrZeroDivisor, err)
}

func TestDiv_Valid(t *testing.T) {
    cases := []struct{ a, b, want int }{
        {10, 2, 5},
        {-9, 3, -3},
        {0, 5, 0},
    }
    for _, c := range cases {
        got, err := Div(c.a, c.b)
        require.NoError(t, err)
        assert.Equal(t, c.want, got)
    }
}

Итоговое задание: сервис пользователей

Финальный штрих — маленький сервис поверх абстрактного хранилища.

Что сделать

  • Реализуйте добавление и получение по ID через интерфейс UserStorage (ниже).
  • Напишите тесты:
    • С реальной реализацией на map (простая память).
    • С моками на testify/mock.
package users

import "errors"

type User struct {
    ID   int
    Name string
}

var ErrNotFound = errors.New("user not found")

type UserStorage interface {
    Save(u User) error
    ByID(id int) (User, error)
}

type Service struct{ s UserStorage }

func New(s UserStorage) *Service { return &Service{s: s} }

func (svc *Service) Add(u User) error { return svc.s.Save(u) }

func (svc *Service) Get(id int) (User, error) { return svc.s.ByID(id) }

Что покрыть в тестах

  • Добавление и получение существующего пользователя.
  • Поведение для несуществующего ID (ошибка ErrNotFound).
  • Для мука: проверка, что методы были вызваны с ожидаемыми аргументами.
Показать решение
package users

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

// Реальная память на map
type memStore struct{ m map[int]User }

func (ms *memStore) Save(u User) error {
    if ms.m == nil {
        ms.m = make(map[int]User)
    }
    ms.m[u.ID] = u
    return nil
}

func (ms *memStore) ByID(id int) (User, error) {
    if u, ok := ms.m[id]; ok {
        return u, nil
    }
    return User{}, ErrNotFound
}

func TestService_WithMemStore(t *testing.T) {
    svc := New(&memStore{})
    u := User{ID: 1, Name: "Hexlet"}
    assert.NoError(t, svc.Add(u))
    got, err := svc.Get(1)
    assert.NoError(t, err)
    assert.Equal(t, u, got)

    _, err = svc.Get(2)
    assert.Error(t, err)
    assert.Equal(t, ErrNotFound, err)
}

// Мок на testify/mock
type mockStore struct{ mock.Mock }

func (m *mockStore) Save(u User) error {
    args := m.Called(u)
    return args.Error(0)
}

func (m *mockStore) ByID(id int) (User, error) {
    args := m.Called(id)
    return args.Get(0).(User), args.Error(1)
}

func TestService_WithMock(t *testing.T) {
    ms := &mockStore{}
    svc := New(ms)

    u := User{ID: 7, Name: "Gopher"}
    ms.On("Save", u).Return(nil).Once()
    ms.On("ByID", 7).Return(u, nil).Once()

    assert.NoError(t, svc.Add(u))
    got, err := svc.Get(7)
    assert.NoError(t, err)
    assert.Equal(t, u, got)

    ms.AssertExpectations(t)
}

Дополнительные материалы

  1. testing — документация
  2. Subtests в Go (статья)
  3. go tool cover — документация
  4. Testify — репозиторий

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff