跨模块测试需用完整模块路径导入并正确配置replace,启动HTTP服务时应轮询检测端口就绪,覆盖率合并须跳过profile头行且路径匹配要精确。

跨模块测试时 import 路径写错导致 “cannot find package”
Go 的模块路径不是文件路径,而是 go.mod 中定义的 module path。如果测试代码在 module-a 里想调用 module-b 的函数,但直接写 import "./../module-b" 或 import "module-b",就会报错。
正确做法是:确保两个模块都已发布(或通过 replace 指向本地路径),并在 module-a/go.mod 中显式添加依赖:
go mod edit -require=example.com/module-b@v0.1.0 go mod edit -replace=example.com/module-b=../module-b
然后在测试文件中用完整模块路径导入:
import (
"example.com/module-b/client"
)- 别用相对路径 import —— Go 不支持
-
replace必须在被测模块的go.mod中声明,不是测试模块的 - 运行
go test前先执行go mod tidy,否则依赖可能未解析
测试中启动真实 HTTP 服务并等待端口就绪
跨模块集成测试常需启动一个模块暴露的 HTTP handler(比如 module-api 提供 REST 接口),再由另一个模块(如 module-client)发起请求。但直接 http.ListenAndServe(":8080", ...) 后立刻发请求,大概率失败——服务还没真正 bind 成功。
立即学习“go语言免费学习笔记(深入)”;
可靠做法是用 net.Listen 占用端口 + 显式启动 goroutine,再轮询检测可连通性:
func startTestServer() (*httptest.Server, error) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
return nil, err
}
srv := &http.Server{Handler: yourHandler}
go srv.Serve(l)
// 等待端口可 dial
for i := 0; i < 50; i++ {
conn, _ := net.Dial("tcp", l.Addr().String())
if conn != nil {
conn.Close()
return httptest.NewUnstartedServer(yourHandler), nil
}
time.Sleep(10 * time.Millisecond)
}
return nil, errors.New("server did not start")
}- 别用固定端口(如 :8080)—— CI 环境可能被占
- 用
httptest.NewUnstartedServer+Start()更轻量,无需真实网络栈 - 真实端口测试必须加超时和重试,TCP 握手有延迟
gomock 生成的 mock 无法跨模块使用
如果 module-core 定义了接口 UserService,而 module-api 想为它生成 mock 用于测试,直接在 module-api 下运行 mockgen 会失败:找不到 module-core 的源码或类型定义。
根本原因是 mockgen 默认只扫描当前模块路径。解决方式有两种:
- 把 mock 文件放在
module-core内(推荐):在module-core/mocks/下生成,导出为公开包,module-api直接 import"example.com/module-core/mocks" - 若必须本地生成,需指定
-source为绝对路径或 go list 输出:mockgen -source=$(go list -f '{{.Dir}}' -m example.com/module-core)/user_service.go -destination=mocks/mock_user.go - 确保
go.mod中module-core版本已更新,且go list能解析到该模块
测试覆盖率统计遗漏跨模块依赖代码
执行 go test -coverprofile=coverage.out ./... 时,默认只覆盖当前模块下的 .go 文件。即使测试调用了 module-db 的函数,它的代码行也不会出现在覆盖率报告中。
要合并多模块覆盖率,必须手动收集每个模块的 profile 并用 go tool cover 合并:
go test -coverprofile=core.coverprofile ./module-core/... go test -coverprofile=api.coverprofile ./module-api/... echo "mode: count" > coverage.out tail -n +2 core.coverprofile >> coverage.out tail -n +2 api.coverprofile >> coverage.out go tool cover -html=coverage.out -o coverage.html
-
tail -n +2是关键:跳过每份 profile 的 header 行 - 路径匹配必须精确,
./module-core/...不能写成./module-core(后者不递归) - CI 中建议用脚本封装,避免漏掉新模块
跨模块测试真正的难点不在语法,而在模块边界如何对齐:版本、路径、构建上下文、甚至 GOPROXY 配置都会让看似简单的 import 失败。每次新增依赖,先确认 go list -m all 能列出目标模块,比盲目改 import 路径更省时间。










