Go: Автоматическое тестирование

Теория: Работа с временными файлами и каталогами

В реальном коде мы всё время крутимся вокруг файлов. Читаем конфиги, пишем логи, сохраняем результаты работы. В тестах это тоже нужно проверять. Но если тесты начнут писать настоящие файлы прямо в проект — начнётся полный бардак. Представь: один тест перезаписал файл, другой не смог его удалить, третий случайно затёр что-то важное. В итоге проект после прогона тестов похож на свалку.

Поэтому в тестах всегда работают с временными файлами и директориями. Они создаются в специальной системной директории 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, так что тесты не мешают друг другу.

Благодаря этому после прогонов тестов на диске не остаётся следов, проект чистый, а тесты надёжные.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845