Вам нужно проверить только публичный контракт Go-пакета, не связывая тест с его внутренними деталями. Какой границы между тестом и пакетом следует придерживаться?
Используйте внешний тестовый пакет с суффиксом _test. Такой тест видит только экспортированные идентификаторы и поэтому проверяет поведение пакета через его публичный API, а не через внутреннюю реализацию.
В Go тесты могут находиться либо в том же пакете, либо во внешнем тестовом пакете. Это разделение решает две разные задачи: проверку внутренних деталей реализации и проверку контракта пакета с позиции его потребителя.
Внешний вариант позволяет писать тесты, которые не получают привилегированный доступ к неэкспортируемым функциям и типам. Благодаря этому рефакторинг внутреннего устройства с меньшей вероятностью ломает тесты без изменения публичного поведения.
Тест в том же пакете может обращаться к неэкспортируемым идентификаторам. Это удобно для проверки сложной внутренней логики, но создаёт связанность: переименование приватной функции или изменение структуры хранения может потребовать переписывать тест даже при сохранении публичного контракта.
Если все тесты сделать внешними, можно потерять возможность напрямую проверять отдельные внутренние алгоритмы. Поэтому выбор границы зависит от того, что именно является предметом проверки: публичное поведение или внутренняя единица реализации.
Для теста публичного контракта объявляйте тестовый файл во внешнем пакете, добавляя к имени основного пакета суффикс _test. Тест импортирует проверяемый пакет так же, как это сделал бы обычный потребитель.
Такой тест не может обратиться к неэкспортируемым defaultConfig или normalize. Это полезная проверка реального публичного API: экспортированные функции, типы, ошибки и наблюдаемое поведение.
Тест в пакете config следует выбирать, когда нужно изолированно проверить внутреннюю функцию, состояние или ветвление, для которых публичного входа недостаточно. Компромисс состоит в том, что внутренние тесты дают более точную локализацию ошибок, но сильнее зависят от реализации.
В библиотеке конфигурации тесты напрямую вызывали приватный парсер. После замены парсера на потоковую реализацию десятки тестов перестали компилироваться, хотя внешний API и результаты для пользователей не изменились.
Рассматривались два варианта. Перенести всё во внешний пакет означало бы потерять узкие тесты парсера; оставить всё внутри сохраняло бы высокую связанность. Выбрали смешанный подход: тесты публичного поведения перенесли в config_test, а небольшой набор тестов внутреннего парсера оставили в config.
В результате рефакторинг внутреннего устройства перестал ломать тесты контракта, а критические алгоритмические детали всё ещё проверялись отдельно. Граница стала явно отражать назначение каждого набора тестов.
1. Вопрос: Даёт ли внешний тестовый пакет доступ к неэкспортируемым идентификаторам через импорт проверяемого пакета?
Нет. Импорт не меняет правила видимости: внешний пакет видит только экспортированные имена. Обходить это ограничение через обычный импорт нельзя; если нужен доступ к внутренностям, тест должен быть частью исходного пакета или проверять поведение через публичный API.
2. Вопрос: Нужно ли все тесты пакета обязательно писать во внешнем тестовом пакете?
Нет. Это не универсальное требование, а выбор уровня проверки. Внешние тесты лучше подходят для стабильного публичного контракта, а внутренние — для локальной проверки неэкспортируемых алгоритмов, инвариантов и труднодоступных ветвей.
3. Вопрос: Может ли внешний тестовый пакет импортировать пакет, который тестирует, без проблемы циклического импорта?
Да, это штатный сценарий для Go-тестов. Тестовый пакет рассматривается отдельно от тестируемого пакета и импортирует его как зависимость; именно поэтому внешний тест не становится частью внутреннего пространства имён исходного пакета.