Кэш в Go: враг тестирования? Разбираем force retesting

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему 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)
Скорость Быстрее (кэш для других пакетов работает) Медленнее (кэш отключен глобально)
Гибкость Можно применить к конкретному тесту Глобальное отключение
Безопасность Средняя (можно забыть флаг) Высокая (кэш точно не вмешается)
Использование