Как писать unit-тесты в Go? Какие инструменты и подходы используются?

Ответ

Unit-тестирование — неотъемлемая часть разработки на Go, и язык предоставляет мощные встроенные инструменты для этого.

Основные инструменты и пакеты

  1. Пакет testing: Стандартная библиотека Go. Все тесты пишутся в файлах с суффиксом _test.go и запускаются командой go test.
  2. testify/assert и testify/require: Самая популярная сторонняя библиотека, которая предоставляет удобные функции для проверок (ассертов). assert продолжает выполнение теста после ошибки, а require немедленно его прерывает.
  3. testify/mock или gomock: Библиотеки для создания моков (mock objects) — заглушек, которые имитируют поведение внешних зависимостей (например, базы данных или API), позволяя изолировать тестируемый код.

Ключевые подходы и паттерны


  1. Table-Driven Tests (Табличные тесты): Это идиоматический подход в Go. Вместо написания отдельной функции для каждого тестового случая, создается срез структур, где каждая структура описывает один тест-кейс (входные данные, ожидаемый результат, название).



  2. Мокирование зависимостей: Для тестирования логики, зависящей от внешних систем, используются интерфейсы. В тестах реальная зависимость подменяется моком, который реализует тот же интерфейс и возвращает предопределенные данные.



  3. Тестирование на гонки данных (Race Condition): Go имеет встроенный детектор гонок. Запуск тестов с флагом go test -race помогает выявить проблемы конкурентного доступа к памяти, что критически важно для многопоточных приложений.



  4. Покрытие кода (Code Coverage): Команда go test -cover показывает, какой процент кода покрыт тестами. Можно сгенерировать детальный HTML-отчет: go test -coverprofile=coverage.out && go tool cover -html=coverage.out.


Пример: Табличный тест

Предположим, у нас есть простая функция Sum:

// file: calculator.go
package main

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

Тест для нее будет выглядеть так:

// file: calculator_test.go
package main

import (
    "testing"
    "github.com/stretchr/testify/assert"
)

func TestSum(t *testing.T) {
    // Описание тестовых случаев
    testCases := []struct {
        name string // Название теста
        a    int    // Входной параметр 1
        b    int    // Входной параметр 2
        want int    // Ожидаемый результат
    }{
        {"Два положительных числа", 2, 3, 5},
        {"Положительное и отрицательное", 5, -3, 2},
        {"Два отрицательных числа", -1, -1, -2},
        {"С нулем", 10, 0, 10},
    }

    // Итерация по тестовым случаям
    for _, tc := range testCases {
        t.Run(tc.name, func(t *testing.T) {
            got := Sum(tc.a, tc.b)
            assert.Equal(t, tc.want, got, "результат должен совпадать с ожидаемым")
        })
    }
}