应根据测试目标选择 sqlmock(验证 SQL 逻辑)或 SQLite 内存模式(验证端到端行为):前者纯内存模拟、速度快、需设 ExpectQuery/Exec 并调用 ExpectationsWereMet;后者真实执行、需手动建表、适合测迁移、映射和事务。
Go 的 database/sql 本身不提供内存数据库或自动 mock 能力,直接测真实数据库容易污染、慢、不可靠;真正可行的方案是用 sqlmock 模拟驱动行为,或用轻量级真实 DB(如 SQLite 内存模式)做集成验证——二者适用场景不同,选错就卡在 CI 或测试稳定性上。
sqlmock 是最常用的测试工具,它替换掉 sql.DB 的底层驱动,让你能精确控制返回结果、检查 SQL 是否被调用、参数是否匹配。它不执行真实 SQL,纯内存模拟,速度快、隔离性好。
常见错误现象:忘记调用 mock.ExpectQuery() 或 mock.ExpectExec() 就执行语句,导致 panic 报 “there is no expectation for…”;或用了 QueryRow().Scan() 却只设了 ExpectQuery(),漏掉 WillReturnRows()。
sqlmock.New() 创建 mock DB,再传给待测函数(不能直接在函数里 sql.Open("postgres", ...))mock.ExpectQuery("SELECT").WithArgs(...).WillReturnRows(...)
mock.ExpectExec("INSERT").WithArgs(...).WillReturnResult(sqlmock.NewResult(1, 1))

mock.ExpectationsWereMet(),否则未触发的 expect 不报错func TestGetUser(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
mock.ExpectQuery(`^SELECT id, name FROM users WHERE id = \?$`).WithArgs(123).
WillReturnRows(sqlmock.NewRows([]string{"id", "name"}).AddRow(123, "alice"))
user, err := GetUser(db, 123)
if err != nil {
t.Fatal(err)
}
if user.Name != "alice" {
t.Error("expected alice")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Error(err)
}
}
当你要验证 migration 是否生效、GORM struct tag 是否正确映射、或事务嵌套逻辑时,sqlmock 太薄——它不解析 SQL,也不校验字段类型。这时用 sqlite3 的 :memory: 模式更合适:真实执行 SQL,但进程退出即销毁,无副作用。
使用场景:测试 DAO 层完整流程、验证外键/索引/默认值、调试“为什么 Scan() 总是 nil”这类底层行为问题。
import _ "github.com/mattn/go-sqlite3"
db, err := sql.Open("sqlite3", ":memory:")
CREATE TABLE),sqlmock 不需要这步,但 SQLite 需要INT 列也能存字符串),和 PostgreSQL/MySQL 行为不一致,别拿它测精度敏感逻辑本地跑得通、CI 失败,十有八九是测试代码里写了 sql.Open("pgx", "host=localhost...")。这种写法让测试强依赖外部服务状态,且无法统一管理凭证或超时。
正确做法是把 *sql.DB 作为参数注入,由测试用例决定用 mock 还是真实 DB:
func CreateUser(db *sql.DB, u User) error,而非内部自己 sql.Open
testDB := setupTestDB(t) 工厂函数,内部根据环境变量切换 mock / sqlite / pggorm.Config{Logger: logger.Default.LogMode(logger.Silent)},否则大量输出干扰断言很多人写事务测试时只调 db.Begin(),然后执行操作,却忘了 tx.Commit() 或 tx.Rollback()。这会导致连接泄漏、后续测试失败,甚至 mock 报 “transaction has already been committed”。
尤其注意嵌套事务(如使用 sql.Tx 传参的 service 层):
Begin() 必须配对 Commit() 或 Rollback(),哪怕测试失败也要 defer 确保执行mock.ExpectBegin() + mock.ExpectCommit() 可验证事务边界是否被正确开启/提交Savepoint 和回滚粒度最难的不是写测试,而是判断该用 mock 还是真实 DB——查 SQL 语法是否拼错?用 sqlmock;查 JOIN 结果字段顺序是否错乱?用 SQLite;查分布式事务下锁等待是否触发超时?那就得上真实的 PostgreSQL 并配好 pg_ctl。别让一种方案包打天下。