发布于2026-07-18 阅读(0)
扫一扫,手机访问
数据库事务的测试,在Go里一直是个容易踩坑的环节。特别是用SQLMock这类工具时,很多细节一旦没注意,测试跑得再绿,也拦不住上线后出问题。这里几个关键点,值得花点时间捋清楚。
SQLMock 不会自动帮你拦截 db.Begin() 调用,这是个关键点。你以为测试没 panic 就万事大吉?其实事务根本没进入 SQLMock 的监控范围。必须显式地告诉它“接下来要 Begin”,否则后续对 *sql.Tx 的任何操作——比如 tx.QueryRow()——都会直接触发 no query expectations were met 错误。
常见的错误写法是先写 mock.ExpectQuery("SELECT..."),再调用 db.Begin()。正确的顺序应该是:
mock.ExpectBegin()tx := db.Begin()tx 的查询或执行操作,需要单独绑定到该事务,比如 mock.ExpectQuery("SELECT...").WithArgs(123).WillReturnRows(...)顺便提一句:ExpectQuery() 默认匹配的是 *sql.DB 上的调用。当绑定到事务时,SQLMock 会自动识别上下文,但前提是 ExpectBegin() 已经触发。
tx.Rollback() 本质上是一个普通的方法调用,SQLMock 默认不会拦截它。如果你不写 mock.ExpectRollback(),哪怕业务代码确实调用了 tx.Rollback(),测试也不会进行校验——更糟糕的是,ExpectationsWereMet() 还会通过,造成“假成功”的错觉。
实际操作中需要注意:
mock.ExpectRollback()tx.Rollback()(比如在 error 分支中),而不是只写了 defer tx.Rollback() 却在中间 return nil 提前提交了mock.ExpectRollback() 不影响实际行为,它只做断言;真实的回滚操作仍然由你的业务代码控制这里有个典型的陷阱:写了 defer tx.Rollback() 后又 return tx.Commit(),结果 Rollback() 根本没有执行。但测试因为漏写了 ExpectRollback(),也不会报错,问题就这么被掩盖过去了。
在 Go 1.19+ 中,QueryRowContext() 默认不被 SQLMock 拦截。即使你写了 mock.ExpectQuery("SELECT..."),也会直接 panic,报 “no query expectations were met”。这并非 bug,而是 SQLMock 的设计限制。
解决办法只有一种:
mock.ExpectQuery("SELECT...") 改成 mock.ExpectQuery("SELECT...").WithContext()tx.QueryRowContext() 的 context.Context 与 Expect 中声明的一致(通常用 context.Background() 就足够了)别想着绕过去:用 QueryRow() 替代,或者删除 context 参数——这样做会让测试和生产行为不一致,反而掩盖了真实问题。
使用 sqlx.StructScan 时,如果失败或字段为空,90% 的可能性是列定义不匹配。SQLMock 不解析 SQL,它只按照你声明的列名去构造返回行,而 StructScan 又严格依赖 struct tag(比如 db:"created_at")和 SQL 返回列名小写完全一致。
需要重点检查的地方:
sqlmock.NewRows([]string{"id", "name", "created_at"}) 中的字符串必须与 SQL SELECT 出来的列名一字不差(包括大小写和下划线)db:"created_at",就不能在 SQL 里写 created_at AS created_at_time,否则 StructScan 找不到对应列sqlmock.NewNullTime(t, true);传 "2026-04-16" 会导致 scan 静默失败,字段值变成零值最容易忽略的细节是:SQL 中使用了别名,但没有同步更新 NewRows 的声明;或者 struct tag 混用了 json:"id" 和 db:"name",导致部分字段映射失败。这些看似不起眼的问题,往往会在关键时刻给你致命一击。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8