При параллельном запуске Go тестов несколько кейсов работают с файлами: какое свойство даёт каждому кейсу о...

При параллельном запуске Go-тестов несколько кейсов работают с файлами: какое свойство даёт каждому кейсу отдельный каталог через t.TempDir?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Отдельный каталог через t.TempDir обеспечивает файловую изоляцию тестовых кейсов: один кейс не читает и не перезаписывает файлы другого. Каталог автоматически удаляется после завершения теста и всех его дочерних subtests.

Исторический контекст

Без специализированного помощника тесту приходилось вручную создавать временный каталог, выбирать уникальное имя и регистрировать очистку через defer или механизм cleanup. Ошибка в любом из этих шагов могла оставить файлы после теста или привести к конфликтам при параллельном запуске.

t.TempDir объединил создание временного каталога и его гарантированную очистку в API тестового фреймворка. Это уменьшает шаблонный код и делает изоляцию частью структуры теста.

Постановка проблемы

Если несколько тестов используют общий путь, они могут перезаписывать один и тот же файл, зависеть от порядка выполнения или видеть остатки данных предыдущего кейса. Такие ошибки особенно трудно воспроизводить: последовательный запуск может проходить, а параллельный — нестабильно падать.

Важно отличать файловую изоляцию от потокобезопасности. Разные каталоги не защищают от гонок в общей памяти, общем внешнем сервисе или глобальной конфигурации процесса.

Подробное решение

Вызывайте t.TempDir внутри конкретного теста или subtest, а не один раз в родительском тесте для всех кейсов. Каждый такой вызов возвращает отдельный временный путь, поэтому относительные имена файлов внутри него не пересекаются.

package app import ( "os" "path/filepath" "testing" ) func TestCases(t *testing.T) { for _, name := range []string{"a", "b"} { t.Run(name, func(t *testing.T) { t.Parallel() dir := t.TempDir() path := filepath.Join(dir, "state") if err := os.WriteFile(path, []byte(name), 0600); err != nil { t.Fatal(err) } }) } }

Вызов внутри callback каждого subtest даёт каждому кейсу свой каталог. После завершения subtest каталог и его содержимое удаляются автоматически.

Если вызвать t.TempDir в родительском тесте и передать один путь всем параллельным subtests, изоляции между ними не будет. Также t.TempDir не очищает ресурсы вне этого каталога и не отменяет фоновые goroutine: соединения, серверы и процессы нужно закрывать отдельно через t.Cleanup или явное завершение.

Ситуация из практики

Набор тестов для загрузчика конфигурации сначала использовал фиксированный путь вроде testdata/current.json. При параллельном запуске один кейс иногда читал файл, который только что записал другой.

Рассматривались три варианта. Отключить параллельность проще всего, но это замедляет тесты и маскирует проблему изоляции. Генерировать уникальные имена файлов вручную возможно, однако остаются вопросы очистки и корректного удаления вложенных файлов. Использовать отдельный каталог через t.TempDir лучше, потому что уникальность и жизненный цикл временных данных передаются тестовому фреймворку.

Выбранный вариант разместил вызов t.TempDir внутри каждого subtest. После этого тесты перестали зависеть от порядка выполнения, а временные файлы не накапливались в рабочем каталоге.

Что кандидаты часто упускают

  1. Обнаружит ли race detector конфликт двух тестов, записывающих один файл?

    Обычно нет. Race detector отслеживает конфликтующие обращения к общей памяти Go, а не логические конфликты операций файловой системы. Два теста могут одновременно менять один файл и не получить отчёт -race, поэтому для файловой изоляции нужны разные каталоги или имена, а для проверки поведения при конкурентной записи — отдельный специализированный тест.

  2. Достаточно ли создать один временный каталог в родительском тесте?

    Нет, если параллельные subtests используют его совместно. Сам каталог будет временным, но файлы внутри останутся общим состоянием. Для независимости вызов t.TempDir должен находиться в каждом subtest, которому нужна собственная файловая среда.

  3. Можно ли полагаться на t.TempDir для очистки внешних ресурсов?

    Нет. Он отвечает за каталог и его содержимое, но не останавливает HTTP-сервер, не закрывает базу данных и не завершает созданную goroutine. Такие ресурсы следует регистрировать через t.Cleanup; cleanup-функции выполняются после теста в обратном порядке регистрации, что позволяет явно задать порядок освобождения зависимых ресурсов.