Проект, с которого я начал go-blueprint, имеет такие модули, как:
./cmd
./api
./main.go (main)
./internal
...
./server
...
./server.go (server -> app/internal/server)
...
...
./tests
./handler_test.go
(Конечно, внутри гораздо больше файлов и папок internal
.)
В настоящее время handler_test.go
тестируется ./internal/server/server.go
, и выполнение go test
запустит этот тест.
Я привык проводить тесты в зеркальной структуре папок, поэтому мне хотелось бы иметь
./internal
./server
./server.go
./tests
./server
./server_test.go
При этом я могу go test app/tests/server
и он работает server_test.go
, но другого теста нет.
Я попробовал использовать t.Run и хотел бы импортировать тестовые функции, однако это не работает:
// ./tests/server_test.go
package tests
import (
testServer "app/tests/server" // ❌ could not import app/tests/server (no required module provides package "app/tests/server")
"testing"
)
func TestServer(t *testing.T) {
t.Run("Testing server routes", testServer.TestBaseRoutes)
}
Возможно, я не понимаю парадигму, исходящую от JUnit, PHPUnit, Jest и т. д., но я, кажется, не понимаю, как Go структурирует тесты, когда используемое приложение имеет средний размер и, возможно, содержит десятки тестовых файлов.
Возможно ли вообще то, что я делаю?
🤔 А знаете ли вы, что...
Go имеет средства для создания и использования модулей (modules) для управления зависимостями.
Тесты Go обычно живут в том же пакете , что и исходные файлы. Даже go-blueprint
недавно исправили свои шаблоны. Если вам нравятся более идиоматические шаблоны проектов, возможно, вам стоит взглянуть на gonew.
Смысл, например, Java для отделения тестов от источников, заключается в том, что тестирование не встроено в язык, и вы не можете развернуть пакет, не включая все в каталог, включая возможные тесты. Таким образом, в этих языках тесты представляют собой отдельные программы в отдельном дереве исходного кода, запускаемые специальной средой тестирования. Кроме того, возможно создание «разделенных пакетов», в которых исходные коды пакета объединены из разных каталогов, что также невозможно в Go.
Таким образом, обходной путь отделения тестов от производственного кода не требуется, и вы можете легко хранить свои тесты вместе с исходным кодом.
Обратите внимание, что все тесты следует хранить вместе с исходным кодом, а не в отдельных папках, даже файлы с пакетом «_test» для тестирования «черного ящика»:
Тестовые файлы, объявляющие пакет с суффиксом «_test», будут скомпилированы как отдельный пакет, а затем скомпоновать его и запустить с основным тестовым двоичным файлом.
См. пример из стандартной библиотеки.
Почему это важно? Во-первых, ожидается, что go test <packages>
проведет все тесты для целевых пакетов, во-вторых (как в примере выше) тестируемые примеры должны появиться в документации пакета.
Go эффективно различает тестирование «белого ящика» и тестирование «черного ящика». При тестировании «белого ящика» тесты находятся в том же пакете, что и код, который вы хотите протестировать, поэтому у вас есть доступ к частным внутренним компонентам. При тестировании «черного ящика», которое, к сожалению, немного недооценивается в онлайн-статьях, вы помещаете тестовый код в отдельный изолированный пакет, поэтому тестовый код имеет доступ только к общедоступному интерфейсу кода, который вы хотите протестировать.
В документации особо отмечается это разделение.
Цитирую:
Тестовый файл может находиться в том же пакете, что и тестируемый, или в соответствующем пакете с суффиксом «_test».
Если файл находится в отдельном пакете «_test», тестируемый пакет должен быть импортирован явно, и только его экспортированные идентификаторы могут быть использовал. Это известно как тестирование «черного ящика».
package abs_test
import (
"testing"
"path_to_pkg/abs"
)
func TestAbs(t *testing.T) {
// test public abs stuff
}
Это соглашение, которое определяет Go.