Почему Go прячет результаты тестов? (И как это ломает жизнь)
Представьте: вы пишете на Go, запускаете go test, видите зеленую галочку... но что-то пошло не так. Вы исправили баг, снова запускаете тесты — и снова зеленый свет. А баг-то остался! Знакомо? Добро пожаловать в мир кэширования тестов Go.
Go — язык, который любит скорость. Чтобы не прогонять одни и те же тесты по десять раз, компилятор кэширует результаты. Звучит удобно, пока вы не столкнетесь с ситуацией, когда кэш тестов Go начинает работать против вас. Особенно это больно бьет по разработчикам, которые работают с интеграционными тестами, файловой системой или внешними API.
В этой статье мы разберем два подхода к решению проблемы: force retesting (принудительный перезапуск) и полное отключение кэша тестов. Вы узнаете, когда что применять, и получите рабочие примеры кода.
Как работает кэширование тестов в Go
Прежде чем ломать, давайте поймем, как это работает. Go кэширует результаты тестов на основе хеша от:
- Исходного кода (
.goфайлов) - Тестовых файлов
- Переменных окружения
- Флагов компиляции
Если ничего не изменилось — Go просто показывает закэшированный результат. Быстро? Да. Опасно? Еще как! Представьте, что ваш тест зависит от времени, случайных чисел или внешнего сервиса. Кэш скажет: «Всё ок!», хотя на самом деле тест давно упал бы.
// Пример теста, который будет ложно зеленым из-за кэша
func TestCurrentTime(t *testing.T) {
now := time.Now()
if now.Year() != 2024 {
t.Errorf("Год должен быть 2024, получен %d", now.Year())
}
}
Запустите этот тест один раз — он пройдет. Исправьте год на 2025 в коде? Кэш все равно покажет старый результат. И вы будете гадать, почему CI падает, а локально всё «работает».
Подход 1: Force retesting (принудительный перезапуск)
Самый популярный способ борьбы с кэшем — флаг -count=1. Это и есть force retesting в чистом виде. Он говорит Go: «Не верь кэшу, перезапусти тест заново».
// Запуск с принудительным перезапуском
go test -count=1 ./...
Почему -count=1, а не -count=0? Потому что -count задает количество прогонов теста. 1 — минимальное значение, которое отключает кэш. 0 просто ничего не запустит.
Когда использовать force retesting
- Интеграционные тесты, которые обращаются к БД или API
- Тесты с random-данными (fuzz-тесты, генерация UUID)
- Тесты, зависящие от времени (таймауты, даты)
- Локальная разработка, когда вы часто меняете код
Пример: интеграционный тест с force retesting
package db_test
import (
"testing"
"time"
)
func TestDatabaseConnection(t *testing.T) {
// Этот тест зависит от живого сервера
// Без -count=1 он может показать старый результат
db := connectToDatabase()
if err := db.Ping(); err != nil {
t.Fatalf("Не удалось подключиться: %v", err)
}
// Имитация временной задержки
time.Sleep(100 * time.Millisecond)
if err := db.Query("SELECT 1"); err != nil {
t.Errorf("Запрос не выполнен: %v", err)
}
}
Запускаем: go test -count=1 -v ./db/. Теперь каждый прогон — честный. Но есть нюанс: force retesting не решает проблему глобально. Если вы забудете флаг — кэш вернется.
Подход 2: Полное отключение кэша тестов
Второй вариант — радикальный. Вы просто говорите Go: «Не кэшируй ничего». Это делается через переменную окружения GOCACHE=off или флаг -count=1 на уровне всей команды.
// Полное отключение кэша
GOCACHE=off go test ./...
Но есть и более тонкий инструмент — go clean -testcache. Он сбрасывает только кэш тестов, не трогая остальной кэш компиляции.
// Сброс кэша тестов
go clean -testcache
go test ./...
Когда отключать кэш полностью
- CI/CD пайплайны — чтобы гарантировать чистоту каждого запуска
- Тестирование перед релизом — когда важна каждая миллисекунда точности
- Отладка странных ошибок — если подозреваете, что кэш врет
Пример: Makefile с отключением кэша
# Makefile для проекта на Go
.PHONY: test clean-test
# Обычный запуск
test:
go test -v ./...
# Запуск с принудительным перезапуском
test-force:
go test -count=1 -v ./...
# Полная очистка и запуск
test-clean:
go clean -testcache
go test -v ./...
# Для CI — максимальная изоляция
ci-test:
GOCACHE=off go test -v -count=1 ./...
Force retesting vs отключение кэша: что выбрать?
Давайте сравним оба подхода в таблице:
| Критерий | Force retesting (-count=1) | Отключение кэша (GOCACHE=off) |
|---|---|---|
| Скорость | Быстрее (кэш для других пакетов работает) | Медленнее (кэш отключен глобально) |
| Гибкость | Можно применить к конкретному тесту | Глобальное отключение |
| Безопасность | Средняя (можно забыть флаг) | Высокая (кэш точно не вмешается) |
| Использование | <