测试不用真实数据库而选内存数据库,因其启动快、无外部依赖、状态易重置,保障测试快速、稳定、可并行;sqlite的:memory:模式最常用,需每个测试用独立*sql.DB实例防污染。

为什么测试中不用真实数据库而选内存数据库
真实数据库启动慢、依赖外部服务、状态难重置,会导致测试变慢、不稳定、难以并行。内存数据库(如 sqlite 的 :memory: 模式、bbolt 内存模式、或纯内存实现的 go-sqlmock + sqlmock 驱动)能绕过 I/O 和网络,让单元测试真正“快”和“隔离”。
用 sqlite 的 :memory: 模式做真实 SQL 测试
这是最常用也最贴近生产环境的做法:用真实 SQL 驱动跑在内存里,既验证 SQL 逻辑,又避免磁盘/连接开销。关键点是每个测试必须用独立的 *sql.DB 实例,否则事务和表结构会互相污染。
-
sqlite3.Open("file::memory:?cache=shared")是基础写法,但注意cache=shared可让多个*sql.DB共享同一内存数据库(仅限单 goroutine 场景) - 更安全的做法是每个测试用独立
:memory:实例,并在测试开始时执行建表语句(例如用db.Exec("CREATE TABLE users(...)")) - 不要复用全局
*sql.DB,否则TestA创建的表可能被TestB误读——Go 的testing.T.Parallel()下尤其危险
func TestUserCreate(t *testing.T) {
db, err := sql.Open("sqlite3", "file::memory:")
if err != nil {
t.Fatal(err)
}
defer db.Close()
_, err = db.Exec(`CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)`)
if err != nil {
t.Fatal(err)
}
// 真实业务逻辑调用
err = CreateUser(db, "alice")
if err != nil {
t.Fatal(err)
}
var count int
err = db.QueryRow("SELECT COUNT(*) FROM users").Scan(&count)
if err != nil || count != 1 {
t.Errorf("expected 1 user, got %d", count)
}
}
用 sqlmock 模拟数据库行为(不执行真实 SQL)
适合验证 DAO 层是否发出了预期 SQL,但不关心 SQL 是否真能运行。它不连接任何数据库,纯 mock,因此无法捕获语法错误或约束冲突。
- 必须用
sqlmock.New()创建*sql.DB,且不能传给sql.Open - 每条期望 SQL 都要显式调用
mock.ExpectQuery()或mock.ExpectExec(),否则测试会 panic - 调用
mock.ExpectationsWereMet()必须放在defer或结尾,否则未触发的期望不会报错
func TestUserCreateWithMock(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
mock.ExpectExec(`INSERT INTO users`).WithArgs("alice").WillReturnResult(sqlmock.NewResult(1, 1))
err = CreateUser(db, "alice")
if err != nil {
t.Fatal(err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Error(err)
}
}
常见踩坑:事务没回滚、连接池干扰、驱动注册遗漏
内存数据库不是“自动干净”的魔法盒。很多问题源于 Go 的 database/sql 默认行为和 SQLite 驱动细节。
传媒企业网站系统使用热腾CMS(RTCMS),根据网站板块定制的栏目,如果修改栏目,需要修改模板相应的标签。站点内容均可在后台网站基本设置中添加。全站可生成HTML,安装默认动态浏览。并可以独立设置SEO标题、关键字、描述信息。源码包中带有少量测试数据,安装时可选择演示安装或全新安装。如果全新安装,后台内容充实后,首页才能完全显示出来。(全新安装后可以删除演示数据用到的图片,目录在https://
立即学习“go语言免费学习笔记(深入)”;
- SQLite 的
:memory:数据库在*sql.DB关闭后即销毁,但若代码中用了db.Begin()却没tx.Commit()或tx.Rollback(),事务会一直挂起,导致后续操作卡住或报database is locked -
sql.Open不建立连接,首次db.Query才真正初始化;如果测试中只sql.Open但没执行任何语句,db实际未生效,容易误判“测试通过” - 忘记
import _ "github.com/mattn/go-sqlite3"会导致sql.Open("sqlite3", ...)报错sql: unknown driver "sqlite3" - 使用
github.com/glebarez/sqlite(纯 Go 实现)替代 cgo 驱动时,路径协议要写成sqlite://:memory:,且不支持cache=shared
最易忽略的是:SQLite 的 :memory: 数据库默认是“连接级私有”,哪怕你用同一个 DSN,两个 sql.Open 返回的 *sql.DB 看不到彼此的表——这不是 bug,是设计。需要共享就得显式加 cache=shared,但要注意它不适用于并发测试。









