Kratos集成测试必须启动真实服务实例,调用app.Run()初始化全部组件,禁用conf.Watch()避免CI panic,并通过接口Mock外部依赖、事务隔离测试数据。

Kratos 服务的自动化集成测试,不是写几个 http.Get 就完事——它必须能真实复现服务启动、依赖注入、中间件链路、数据库/缓存连接、gRPC/HTTP 协议交互这整套运行时环境。否则测了也白测,上线照样挂。
用 kratos/pkg/test 启动真实服务实例
Kratos 官方 pkg/test 提供了轻量但关键的测试辅助能力,核心是绕过 main 函数、直接拉起完整服务上下文。不推荐用 httptest.NewServer 模拟,它无法加载 Kratos 的配置、中间件、日志、trace 等运行时契约。
- 必须调用
app.Run()启动真实服务,哪怕只跑几秒:它会触发所有组件初始化(包括数据库连接池、注册中心心跳、HTTP/gRPC Server 绑定) - 在测试中用
app.New()构建 app 实例,传入自定义配置(比如把数据库地址换成localhost:54321测试专用实例) - 务必调用
app.Close()清理资源,否则端口占用、goroutine 泄漏会导致后续测试失败 - 示例片段:
func TestUserCreate(t *testing.T) { app := initApp(t) // 自定义初始化函数,返回 *app.App defer app.Close() // 关键!不能漏 <pre class="brush:php;toolbar:false;">// 等待 HTTP server 真正就绪(可加简单健康检查) time.Sleep(100 * time.Millisecond) client := http.DefaultClient resp, err := client.Post("http://localhost:8000/v1/users", "application/json", strings.NewReader(`{"name":"test"}`)) if err != nil { t.Fatal(err) } defer resp.Body.Close()}
Mock 外部依赖要用 gomock + entgo 接口抽象
集成测试 ≠ 全链路跑通所有下游。数据库、Redis、第三方 API 必须隔离,否则测试不稳定、慢、不可并行。Kratos 生态里最稳妥的方式,是基于接口做 Mock,而不是 patch HTTP client 或改 config。
- Ent 框架生成的 repo 接口(如
UserRepo)天然支持 mock:用gomock生成 mock 实现,注入到 service 层 - 不要 mock
*ent.Client,而要 mock 上层封装的UserRepo接口——这样既保留业务逻辑验证,又切断 DB 依赖 - 对于 gRPC 下游服务,用
buf connect-go的mock工具生成 client mock,或直接用grpc-go的testutils启一个 fake server - 常见错误:在测试里硬编码
redis.Dial("localhost:6379")→ 本地没 Redis 就 panic;应统一通过redis.NewClient()接口注入,测试时替换为redis.NewMockClient()
测试数据清理必须和事务绑定,不能靠 TRUNCATE
Kratos 服务常使用事务控制一致性,但集成测试里如果每个 case 都用 TRUNCATE users,会破坏事务边界、干扰并发测试,还可能被其他测试进程误删数据。
- 正确做法:每个测试用例开启独立事务(
tx, _ := db.BeginTx(ctx, nil)),把 repo 注入该 tx,case 结束后tx.Rollback() - Ent 支持
ent.Tx类型,所有操作传入该 tx 实例即可自动绑定 - 避免用
db.Exec("DELETE FROM ..."),它不走 Ent 的 hook 和 validator,容易漏掉业务约束 - 如果必须清库(比如测试迁移脚本),用专用测试数据库 + 每次测试前重建 schema(
ent.Schema.Create(context.Background())),而非复用开发库
CI 中跑集成测试要严格隔离端口和配置文件
本地能过不代表 CI 能过。Kubernetes 环境下多个测试 job 并发执行时,端口冲突、配置覆盖、临时目录竞争是高频失败原因。
- HTTP/gRPC 端口必须动态分配:
port, _ := net.Listen("tcp", ":0"),再从port.Addr().(*net.TCPAddr).Port取真实端口 - 配置文件不能写死路径,用
os.Setenv("CONFIG_PATH", "config/test.yaml")+conf.Load()加载 - 日志和 trace 输出设为
io.Discard,避免大量 I/O 拖慢 CI;但保留 error 级别输出到t.Log() - 最关键的坑:Kratos 的
conf.Watch()在测试中默认开启 fsnotify,CI 容器里常因 inotify 限制 panic;测试中应显式禁用:conf.Load(conf.WithWatcher(nil))
真正难的从来不是“怎么写断言”,而是让测试环境无限逼近线上——服务启动方式、依赖注入顺序、中间件执行时机、错误传播路径,这些细节一旦错位,测试就只是自我安慰。Kratos 的集成测试,本质是在验证你对框架运行时契约的理解是否准确。











