Что такое unit тесты?go-65

Unit-тесты (модульные тесты) — это фундаментальная практика тестирования отдельных изолированных компонентов (юнитов) программного обеспечения. В контексте Go это означает тестирование отдельных функций, методов или небольших модулей кода.

1. Основные характеристики unit-тестов

  • Изолированность: Тестируют один компонент без зависимостей
  • Детерминированность: Всегда дают одинаковый результат при одинаковых входных данных
  • Быстрота выполнения: Должны работать мгновенно
  • Автоматизация: Могут запускаться без ручного вмешательства

2. Как выглядят unit-тесты в Go

Пример тестируемой функции:

// math.go
package math

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

Соответствующий unit-тест:

// math_test.go
package math

import "testing"

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

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

3. Ключевые принципы написания unit-тестов в Go

3.1. Использование табличных тестов

  • Позволяют компактно описывать множество тест-кейсов
  • Упрощают добавление новых случаев
  • Делают тесты более читаемыми

3.2. Изоляция зависимостей

Для изоляции тестируемого кода от внешних зависимостей используются:

  • Моки (ручные или с помощью библиотек вроде gomock)
  • Тестовые заглушки (stubs)
  • Фейковые реализации (fakes)

Пример с интерфейсом:

type Storage interface {
    Get(id int) (string, error)
}

func Process(s Storage, id int) (string, error) {
    data, err := s.Get(id)
    if err != nil {
        return "", err
    }
    return "Processed: " + data, nil
}

Тест с моком:

type mockStorage struct {
    data string
    err  error
}

func (m *mockStorage) Get(id int) (string, error) {
    return m.data, m.err
}

func TestProcess(t *testing.T) {
    tests := []struct {
        name     string
        data     string
        err      error
        expected string
        wantErr  bool
    }{
        {"success", "test", nil, "Processed: test", false},
        {"error", "", errors.New("not found"), "", true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            storage := &mockStorage{data: tt.data, err: tt.err}
            got, err := Process(storage, 1)

            if (err != nil) != tt.wantErr {
                t.Fatalf("unexpected error: %v", err)
            }
            if got != tt.expected {
                t.Errorf("got %q, want %q", got, tt.expected)
            }
        })
    }
}

4. Пакеты для тестирования в Go

  • testing - стандартный пакет
  • testify/assert - удобные assertions
  • gomock - генерация моков
  • httptest - тестирование HTTP-обработчиков
  • sqlmock - тестирование SQL-запросов

5. Best Practices

  1. Именование тестов: Test[Функция]_[Сценарий]
  2. Тестирование ошибок: Обязательно тестируйте обработку ошибок
  3. Покрытие кода: Стремитесь к 70-80% покрытия (но не делайте 100% самоцелью)
  4. Читаемость: Тесты должны быть понятными без комментариев
  5. Поддержка: Тесты — такой же код, который нужно поддерживать

Резюмируем

Unit-тесты в Go — это:

  • Быстрые изолированные тесты отдельных компонентов
  • Стандартизированный подход через пакет testing
  • Лучшие практики: табличные тесты, моки, четкие assertions
  • Неотъемлемая часть профессиональной разработки на Go

Хорошие unit-тесты экономят время на отладку, предотвращают регрессии и служат живой документацией к коду.