- Директория tmp
- Временный файл: создаём, пишем, читаем
- Временная директория: создаём и работаем внутри
- Современный способ: t.TempDir()
- Зачем всё это
- Итог
В реальном коде мы всё время крутимся вокруг файлов. Читаем конфиги, пишем логи, сохраняем результаты работы. В тестах это тоже нужно проверять. Но если тесты начнут писать настоящие файлы прямо в проект — начнётся полный бардак. Представь: один тест перезаписал файл, другой не смог его удалить, третий случайно затёр что-то важное. В итоге проект после прогона тестов похож на свалку.
Поэтому в тестах всегда работают с временными файлами и директориями. Они создаются в специальной системной директории tmp
, имеют уникальные имена и исчезают после завершения теста. Это как одноразовая посуда: использовал, проверил — выкинул.
Директория tmp
У каждой операционной системы есть место под временные данные. На Linux и macOS это обычно /tmp
, на Windows — C:\Users<имя>\AppData\Local\Temp
. Туда можно писать что угодно, и система рано или поздно сама почистит.
Go знает, где этот каталог, через функцию os.TempDir()
. Если её вызвать, она вернёт путь к текущему «времяному».
fmt.Println(os.TempDir())
// Linux: /tmp
// Windows: C:\Users\Artur\AppData\Local\Temp
Когда мы вызываем os.CreateTemp("", "pattern")
или os.MkdirTemp("", "pattern")
и передаём пустую строку первым аргументом, Go автоматически создаёт файл или директорию именно там.
Временный файл: создаём, пишем, читаем
Допустим, у нас есть функция, которая должна записывать данные в файл. Мы хотим протестировать её, но писать в проект нельзя. Тогда используем временный файл.
func TestTempFile(t *testing.T) {
// Создаём временный файл в системной директории tmp.
// Второй аргумент — шаблон имени. Звёздочка заменится на случайные символы,
// так что два теста никогда не получат один и тот же файл.
f, err := os.CreateTemp("", "example-*.txt")
if err != nil {
t.Fatal(err)
}
// Файл создаётся реально на диске. Чтобы он не остался после теста,
// регистрируем удаление. Remove сработает в конце функции.
defer os.Remove(f.Name())
// Записываем строку в файл. Здесь всё как обычно.
_, err = f.WriteString("hello, world")
if err != nil {
t.Fatal(err)
}
// Теперь читаем содержимое обратно через имя файла.
data, err := os.ReadFile(f.Name())
if err != nil {
t.Fatal(err)
}
// Сравниваем результат.
if string(data) != "hello, world" {
t.Errorf("получили %q, хотели %q", string(data), "hello, world")
}
}
Такой тест работает в изоляции: каждый запуск создаёт уникальный файл, тест проверяет его содержимое и удаляет. После тестов в проекте чисто, будто ничего и не было.
Временная директория: создаём и работаем внутри
Иногда одного файла мало. Например, если нужно проверить функцию, которая перебирает файлы в директории. Для этого в Go есть os.MkdirTemp
.
func TestTempDir(t *testing.T) {
// Создаём временный каталог. Имя будет вроде /tmp/exampledir-12345.
dir, err := os.MkdirTemp("", "exampledir-*")
if err != nil {
t.Fatal(err)
}
// Удаляем директорию и всё содержимое после теста.
defer os.RemoveAll(dir)
// Внутри этой директории создаём файл.
path := filepath.Join(dir, "data.txt")
err = os.WriteFile(path, []byte("test data"), 0644)
if err != nil {
t.Fatal(err)
}
// Читаем обратно и проверяем.
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(data) != "test data" {
t.Errorf("получили %q, хотели %q", string(data), "test data")
}
}
Теперь тест создаёт целую «песочницу»: в ней можно создавать файлы, директории, проверять логику работы с файловой системой. В конце всё это удаляется одной строкой os.RemoveAll
.
Современный способ: t.TempDir()
Писать вручную os.MkdirTemp
и defer os.RemoveAll
немного утомительно. Поэтому начиная с Go 1.15 у *testing.T
есть метод TempDir()
. Он делает всё то же самое, но за вас.
func TestWithTempDir(t *testing.T) {
// Создаём временную директорию. Go сам удалит её после теста.
dir := t.TempDir()
// Кладём туда файл.
path := filepath.Join(dir, "config.json")
os.WriteFile(path, []byte(`{"ok": true}`), 0644)
// Читаем обратно.
data, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(data) != `{"ok": true}` {
t.Errorf("получили %q, хотели %q", string(data), `{"ok": true}`)
}
}
С t.TempDir
тесты получаются чище: не нужно вручную писать RemoveAll
, не нужно думать про порядок defer. Go всё делает за вас.
Зачем всё это
Временные файлы и директории нужны везде, где код взаимодействует с файловой системой:
- сохранить результат работы и проверить, что он записан правильно;
- проверить алгоритм, который читает директорию с файлами;
- временно создать лог или отчёт и убедиться, что он имеет правильный формат.
Тесты должны быть изолированными, и tmp
— идеальный способ эту изоляцию обеспечить.
Итог
Вместо того чтобы засорять проект настоящими файлами, тесты работают с временными. Функция os.CreateTemp
создаёт файл, os.MkdirTemp
— директорию, t.TempDir
делает то же самое, но сам убирает за собой. Все они создают уникальные имена внутри системного tmp
, так что тесты не мешают друг другу.
Благодаря этому после прогонов тестов на диске не остаётся следов, проект чистый, а тесты надёжные.
Самостоятельная работа
Протестируем маленькую утилиту: она складывает строки лога во временный файл, чтобы потом его прикрепить к письму/репорту.
package tempfiledemo
import (
"os"
"strings"
)
// WriteLinesToTemp пишет строки в новый временный файл и возвращает путь к нему.
func WriteLinesToTemp(prefix string, lines []string) (string, error) {
f, err := os.CreateTemp("", prefix)
if err != nil {
return "", err
}
defer f.Close()
content := strings.Join(lines, "\n")
if _, err := f.WriteString(content); err != nil {
return "", err
}
return f.Name(), nil
}
Напишите тесты, которые убеждаются, что файл создан, данные на месте, и всё корректно удаляется.
Что тестируем
- Что функция создаёт файл в системной temp‑директории и записывает все строки в правильном порядке (через
\n
). - Что файл закрывается и его можно безопасно удалить.
Что покрыть в тестах
- Базовый кейс:
[]string{"hello"}
→ файл содержитhello
. - Несколько строк: проверяем разделитель
\n
и порядок. - Пустой список: создаётся пустой файл.
Подсказка
- После
os.Remove(path)
можно проверитьos.Stat
наos.ErrNotExist
.
Показать решение
package tempfiledemo
import (
"errors"
"os"
"testing"
)
func TestWriteLinesToTemp(t *testing.T) {
t.Run("single line", func(t *testing.T) {
path, err := WriteLinesToTemp("log-", []string{"hello"})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.Remove(path)
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read: %v", err)
}
if string(data) != "hello" {
t.Fatalf("got %q, want %q", data, "hello")
}
})
t.Run("multi line and empty", func(t *testing.T) {
cases := []struct {
lines []string
want string
}{
{[]string{"a", "b", "c"}, "a\nb\nc"},
{nil, ""},
}
for _, c := range cases {
path, err := WriteLinesToTemp("log-", c.lines)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read: %v", err)
}
if string(data) != c.want {
t.Fatalf("got %q, want %q", data, c.want)
}
if err := os.Remove(path); err != nil {
t.Fatalf("remove: %v", err)
}
if _, err := os.Stat(path); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("expected not exists, got: %v", err)
}
}
})
}
Дополнительные материалы
- os.CreateTemp — документация
- testing.T.TempDir — временный каталог в тестах
- os.Remove — удаление файлов
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.