跳转到主内容
新派编程网 - 新潮编程理念,全新技术课堂!

Golang单元测试是否应该访问数据库

单元测试中应避免直接调用 database/sql 或 gorm.DB,因其破坏快、稳、可重复、隔离性;推荐用接口抽象+mock(如 testify/mock)替代;集成测试才连真实数据库,并严格管控生命周期与清理。 单元测试里直接调用
database/sql
或
gorm.DB
通常是个坏主意 不是“不能”,而是会破坏单元测试的核心价值:快、稳、可重复、隔离。一旦测试依赖真实数据库,就引入了网络延迟、状态残留、并发冲突、 环境配置 等问题。CI 上跑一次测试可能从 200ms 涨到 8 秒,失败原因可能是 PostgreSQL 没启、连接池满、或上一条测试没清库。 常见错误现象包括:
ERROR: database "testdb" is being accessed by other users
测试间相互污染,比如
TestCreateUser
插入数据后,
TestListUsers
返回意外结果 本地能过,CI 失败——因为 Docker Compose 启动慢或
pg_isready
检查漏了 用接口抽象 + mock 替代真实 DB 调用 Go 的接口天然适合解耦。把 数据访问 逻辑封装进接口(如
UserRepository
),测试时传入 mock 实现,而非真实
*sql.DB
或
*gorm.DB
。 实操建议: 立即学习 “ go语言免费学习笔记(深入) ”; 定义接口时只暴露业务需要的方法,例如
GetByID(ctx, id) (*User, error)
,不要暴露
Exec
、
QueryRow
等底层操作 用
github.com/stretchr/testify/mock
或
gomock
生成 mock,避免手写大量桩代码 mock 行为要覆盖成功路径和典型错误,比如模拟
sql.ErrNoRows
或超时 context
type UserRepository interface { GetByID(context.Context, int) (*User, error) } // 测试中 mockRepo := new(MockUserRepository) mockRepo.On("GetByID", mock.Anything, 123).Return(&User{ID: 123, Name: "Alice"}, nil) service := NewUserService(mockRepo) user, _ := service.FindUser(context.Background(), 123) assert.Equal(t, "Alice", user.Name) mockRepo.AssertExpectations(t)
集成测试才该连真实数据库,且必须严格管控 如果真要验证 SQL 语句、事务行为或 GORM 关联逻辑,应该单独建
xxx_integration_test.go
文件,在明确的生命周期内启动轻量数据库(如
docker run -d -p 5432:5432 -e POSTGRES_PASSWORD=pass postgres:15-alpine
),并确保每次测试前清空 schema 或使用临时数据库。 Git 程序猿必备版本控制工具 下载 关键控制点: 用
go test -run Integration
单独执行,不混入单元测试流程 测试函数名以
Integration
开头,例如
TestIntegration_CreateUser_WithRole
数据库连接字符串通过 环境变量 注入,CI 和本地用不同实例,避免误删生产数据 每个测试结束后执行
TRUNCATE TABLE users, roles CASCADE
,不用
DROP/CREATE
避免权限问题 特殊场景:SQLite 内存模式 ≠ 单元测试友好 有人用
sqlite3.Open("file::memory:")
图省事,但它仍有副作用:多个
*sql.DB
实例不共享内存数据库,事务隔离级别与 PostgreSQL 不同,且无法测试驱动特定行为(如 pgx 的
pgtype
类型转换)。 更隐蔽的问题: 内存 SQLite 在 fork 后失效,导致
go test -race
下行为异常 无法复现
context.DeadlineExceeded
在真实网络延迟下的表现 GORM 的
Preload
在 SQLite 中不触发外键约束,掩盖了 PostgreSQL 下的 N+1 或 join 错误 真正需要验证查询逻辑时,优先选
sqlmock
拦截
Query
/
Exec
调用并断言 SQL 字符串,而不是换数据库。

相关文章