Тесты в подпапках Go: полный разбор для начинающих

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

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

Начать курс

Тесты в подпапках Go: решаем проблему, о которой молчат туториалы

Вы когда-нибудь писали тесты в Go и сталкивались с тем, что они отлично работают в корне проекта, но стоит перенести их в подпапку — всё ломается? Знакомо? Если да — вы не одиноки. Вопрос "Golang tests in sub-directory" на Stack Overflow собрал тысячи просмотров. И это неудивительно: документация Go говорит об этом сухо, а начинающие разработчики часто теряются.

В этой статье мы разберёмся, как правильно организовать тестирование Go с использованием вложенных пакетов, зачем это нужно и как не наступить на грабли. Поехали!

Почему тесты в подпапках — это боль?

Представьте: у вас есть проект, структура которого выглядит так:

myapp/
├── main.go
├── go.mod
└── utils/
    ├── math.go
    └── math_test.go

Вы пишете Go test в корне — и всё работает. Но когда вы пытаетесь запустить тесты из папки utils, Go ругается: "package myapp/utils is not in GOROOT". Знакомая ошибка?

Проблема в том, что Go работает с пакетами, а не с папками. Когда вы создаёте подпапку, вы фактически создаёте новый пакет. И тесты в нём нужно запускать с учётом модульной структуры проекта. Но давайте по порядку.

Основы: как Go видит тесты

В Go тесты — это обычные файлы с суффиксом _test.go. Они могут находиться:

  • В том же пакете (белые тесты) — имеют доступ к неэкспортированным функциям.
  • В отдельном пакете с суффиксом _test (чёрные тесты) — тестируют пакет как внешний пользователь.

Когда вы помещаете тесты в подпапку, вы создаёте новый пакет. Например, папка utils — это пакет utils. И тесты в ней должны принадлежать этому пакету.

Пример правильной структуры

myapp/
├── go.mod
├── main.go
└── utils/
    ├── math.go        // package utils
    └── math_test.go   // package utils

В файле math.go:

package utils

func Add(a, b int) int {
    return a + b
}

В файле math_test.go:

package utils

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Expected 5, got %d", result)
    }
}

Теперь запустите тесты из корня проекта:

go test ./utils/

Или из папки utils:

cd utils
go test

Всё работает! Но что если у вас более сложная структура?

Вложенные подпапки: глубже, чем кажется

Допустим, у вас есть проект с такой структурой:

myapp/
├── go.mod
├── main.go
└── services/
    ├── user/
    │   ├── user.go
    │   └── user_test.go
    └── order/
        ├── order.go
        └── order_test.go

Здесь каждая подпапка — отдельный пакет. Чтобы запустить все тесты рекурсивно, используйте три точки:

go test ./...

Эта команда найдёт все пакеты в проекте и запустит их тесты. Очень удобно для CI/CD!

Что если тесты в той же папке, что и код?

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

Типичные ошибки и их решения

Давайте разберём самые частые проблемы, с которыми сталкиваются новички при работе с Golang tests in sub-directory.

Ошибка 1: "package is not in GOROOT"

Причина: Вы пытаетесь запустить тест из подпапки, не указав модуль. Go не знает, к какому модулю относится пакет.

Решение: Убедитесь, что у вас есть go.mod в корне проекта. И запускайте тесты через go test ./папка/ из корня.

Ошибка 2: циклические зависимости

Причина: Пакет A импортирует пакет B, а пакет B импортирует пакет A. В Go такое запрещено.

Решение: Пересмотрите архитектуру. Вынесите общие типы в отдельный пакет.

Ошибка 3: тесты не видят функции из родительского пакета

Причина: Подпапка — это отдельный пакет. Функции из родительского пакета нужно импортировать.

Решение: Используйте импорт по полному пути модуля:

import "myapp/utils"

Best practices для организации тестов

Опираясь на опыт сообщества и документацию Go, вот несколько советов, которые помогут вам писать чистые и поддерживаемые тесты:

  • Используйте go test ./... для запуска всех тестов — это стандарт в Go-проектах.
  • Называйте тестовые файлы осмысленно: user_test.go, order_service_test.go.
  • Группируйте тесты по функциональности: если пакет большой, разбейте тесты на несколько файлов.
  • Используйте t.Run() для подтестов — это улучшает читаемость и позволяет запускать отдельные кейсы.
  • Не бойтесь выносить тестовые данные в отдельные файлы — например, testdata/.

Пример с подтестами

package utils

import "testing"

func TestAdd(t *testing.T) {
    tests := []struct {
        name string
        a, b int
        want int
    }{
        {"positive", 2, 3, 5},
        {"negative", -1, -2, -3},
        {"zero", 0, 0, 0},
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            if got := Add(tt.a, tt.b); got != tt.want {
                t.Errorf("Add() = %d, want %d", got, tt.want)
            }
        })
    }
}

Продвинутые техники: тесты с зависимостями

В реальных проектах код часто зависит от баз данных, API или файлов. Как тестировать такие

Блоги

Рекомендаций по книгам