Ответ
Unit-тестирование — неотъемлемая часть разработки на Go, и язык предоставляет мощные встроенные инструменты для этого.
Основные инструменты и пакеты
- Пакет
testing: Стандартная библиотека Go. Все тесты пишутся в файлах с суффиксом_test.goи запускаются командойgo test. testify/assertиtestify/require: Самая популярная сторонняя библиотека, которая предоставляет удобные функции для проверок (ассертов).assertпродолжает выполнение теста после ошибки, аrequireнемедленно его прерывает.testify/mockилиgomock: Библиотеки для создания моков (mock objects) — заглушек, которые имитируют поведение внешних зависимостей (например, базы данных или API), позволяя изолировать тестируемый код.
Ключевые подходы и паттерны
Table-Driven Tests (Табличные тесты): Это идиоматический подход в Go. Вместо написания отдельной функции для каждого тестового случая, создается срез структур, где каждая структура описывает один тест-кейс (входные данные, ожидаемый результат, название).
Мокирование зависимостей: Для тестирования логики, зависящей от внешних систем, используются интерфейсы. В тестах реальная зависимость подменяется моком, который реализует тот же интерфейс и возвращает предопределенные данные.
Тестирование на гонки данных (Race Condition): Go имеет встроенный детектор гонок. Запуск тестов с флагом
go test -raceпомогает выявить проблемы конкурентного доступа к памяти, что критически важно для многопоточных приложений.Покрытие кода (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, "результат должен совпадать с ожидаемым")
})
}
}