Avatar ·

Как правильно организовать и запускать тесты в Go для пакетов во вложенных поддиректориях?

Всем привет! Я осваиваю Go и столкнулся с проблемой при написании тестов для проекта, который имеет структуру с поддиректориями. Мой проект выглядит так: ``` myapp/ ├── main.go ├── handlers/ │ ├── user.go │ └── user_test.go ├── models/ │ ├── user.go │ └── user_test.go └── utils/ └── helpers.go ``` В файле `handlers/user_test.go` я пытаюсь импортировать функции из `utils/helpers.go`, но при запуске `go test ./...` из корня проекта тесты падают с ошибкой вроде "undefined: SomeFunction" или "package myapp/utils is not in GOROOT". Если я запускаю `go test` изнутри папки `handlers/`, то тесты работают, но только если в них нет импортов из других моих пакетов. Я читал, что Go сам находит тесты, но никак не могу понять, как правильно связать тесты в поддиректориях с другими частями проекта. Подскажите, как правильно организовать тесты в поддиректориях? Нужно ли как-то специально настраивать go.mod или пути импорта? И как запускать все тесты разом, чтобы они работали корректно? Буду благодарен за пример кода и объяснение. <<>>

Основная причина проблемы

Ошибка возникает из-за неправильного указания путей импорта. В Go, начиная с версии 1.11, все проекты должны использовать модули (go.mod). Если вы не инициализировали модуль, Go не может найти ваши внутренние пакеты. Вторая распространённая ошибка — использование относительных путей вместо полного имени модуля.

Пошаговое решение

Вот как правильно организовать тесты в поддиректориях:

1. Инициализируйте модуль

В корне проекта выполните:

go mod init myapp

Это создаст файл go.mod с именем модуля myapp. Теперь все пакеты внутри проекта будут импортироваться через этот базовый путь.

2. Правильные импорты в тестах

Допустим, ваш utils/helpers.go содержит:

package utils

func HelperFunc() string {
    return "hello from utils"
}

Тогда в handlers/user_test.go импорт должен выглядеть так:

package handlers

import (
    "testing"
    "myapp/utils"  // полный путь от корня модуля
)

func TestHandler(t *testing.T) {
    result := utils.HelperFunc()
    if result != "hello from utils" {
        t.Errorf("unexpected result: %s", result)
    }
}

3. Запуск всех тестов

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

go test ./...

Троеточие означает "все поддиректории рекурсивно". Go сам найдёт все файлы *_test.go во всех пакетах.

Дополнительные рекомендации

  • Имена пакетов: Убедитесь, что файлы в одной директории имеют одинаковый package name. Например, все файлы в handlers/ должны быть package handlers.
  • Циклические импорты: Если пакет A импортирует пакет B, а B импортирует A — Go выдаст ошибку. Реорганизуйте код или вынесите общие зависимости в третий пакет.
  • Тесты в той же папке: Файлы *_test.go могут быть либо в том же пакете (тестируют внутренние функции), либо в отдельном пакете с суффиксом _test (например, package handlers_test). Второй вариант позволяет тестировать только экспортируемые функции.

Пример рабочей структуры

После исправлений ваша структура будет работать так:

myapp/
├── go.mod          # module myapp
├── main.go
├── handlers/
│   ├── user.go
│   └── user_test.go  # import "myapp/utils"
├── models/
│   ├── user.go
│   └── user_test.go  # import "myapp/utils"
└── utils/
    └── helpers.go    # package utils

Запуск go test ./... из корня отработает без ошибок и покажет результаты для всех пакетов.

<<>>

Альтернативный подход: тесты внутри пакета с суффиксом _test

Если вы хотите тестировать только публичный API пакета (экспортируемые функции), используйте отдельный тестовый пакет. Например, создайте файл handlers/user_external_test.go:

package handlers_test  // обратите внимание на суффикс _test

import (
    "testing"
    "myapp/handlers"
    "myapp/utils"
)

func TestExternalHandler(t *testing.T) {
    // Тестируем только экспортируемые функции handlers
    result := handlers.SomeExportedFunc()
    utils.HelperFunc() // тоже работает
}

Такой подход изолирует тесты от внутренних деталей реализации пакета и заставляет вас тестировать через публичный интерфейс. Это считается хорошей практикой для unit-тестов.

Использование go test с флагами

Для более гибкого запуска тестов в поддиректориях используйте флаги:

  • go test -v ./handlers/... — только тесты в handlers и её подпапках
  • go test -run TestName ./... — запустить конкретный тест по имени
  • go test -count=1 ./... — отключить кеширование результатов

Это особенно полезно, когда у вас много тестов и нужно запускать только определённые.