最轻量可控的本地集成测试数据库配置是sqlite3的:memory:模式配合事务回滚,每次测试获得全新空实例、天然隔离、零延迟、真实执行sql,避免数据污染与环境依赖。

直接用 sqlite3 的 :memory: 模式,配合事务回滚,就是最轻量、最可控的本地集成测试数据库配置方式。它不依赖外部服务、启动零延迟、每个测试完全隔离,且能真实执行 SQL——比 mock 更贴近生产行为,又比 Docker 启容器更省资源。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
为什么不用 MySQL/PostgreSQL 本地实例做集成测试
常见错误是开发机上装个 MySQL,测试里硬编码 localhost:3306。这会导致:
- 多人协作时 DSN 不一致,CI 环境根本没装数据库,go test 直接 panic
- 测试间数据互相污染,DELETE 清表慢、漏删外键、事务未提交就 exit,下个测试直接失败
- 无法并行运行(go test -p=4),因为所有测试共用同一张表
- 一次测试失败后,数据库残留脏数据,后续测试行为不可预测
用 sqlite3 + :memory: 搭建可复位的测试 DB
关键不是“连上数据库”,而是“每次测试都拿到一个全新、空的、仅内存存在的 DB 实例”。
database/sql 原生支持,无需额外 driver 或 ORM:
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
<p>// 创建表结构(必须显式执行)
_, err = db.Exec(<code>CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)</code>)
if err != nil {
t.Fatal(err)
}
</p>
注意:
- :memory: 是 SQLite 内置机制,不是文件路径,不会写磁盘
- 每次 sql.Open 都生成独立内存实例,不同测试之间天然隔离
- 不要复用全局 *sql.DB 变量,必须在每个测试函数内创建
用事务包裹操作,避免手动清表
即使用了 :memory:,多个子测试(t.Run)共用同一个 db 仍可能互相干扰。正确做法是:每个子测试开启独立事务,结尾 Rollback:
tx, err := db.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback() // 不用管是否成功,失败也 Rollback
<p>// 把 tx 传给你的 repo(需接收 <em>sql.Tx 而非 </em>sql.DB)
repo := &UserRepository{DB: tx}</p><p>// 执行插入、查询等操作
err = repo.Create(context.Background(), &User{Name: "alice"})
if err != nil {
t.Fatal(err)
}
</p>
这样做的好处:
- 不需要 TRUNCATE 或 DROP TABLE,回滚是原子、瞬时、无副作用的
- 即使测试 panic,defer tx.Rollback() 仍会执行
- 支持外键、触发器、约束检查,能暴露真实 SQL 逻辑问题
- 唯一限制:不能在事务中执行 CREATE TABLE 等 DDL(SQLite 会隐式提交)——建表必须放在 tx 外
什么时候该切到 Docker 容器?
只有当你的 SQL 用到了 SQLite 不支持的特性时才升级:
- PostgreSQL 的 jsonb、pg_trgm、ON CONFLICT DO UPDATE
- MySQL 的全文索引、特定字符集或存储引擎行为
- 需要验证连接池、超时、重试等网络层表现
这时用 testcontainers-go 启一个临时容器,但只用于少数关键测试(加 //go:build integration 标签),日常开发和 CI 主流仍走 :memory:。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










