如何使用sqlmock测试Goroutine中的PostgreSQL查询?
解决Goroutine并发查询的sqlmock测试问题
首先先指出你代码里的一个明显bug:三个Goroutine里都把查询结果赋值给了users变量,services和sells根本没被赋值,最终返回的模型里这两个字段都是0。先修正这个问题,让每个协程对应自己的变量,同时把Find换成更适合单行单列查询的Scan:
func(r repository) Queries(ctx context.Context) (*models.MyModel, error) { var users int64 var services int64 var sells int64 queryResponses := make(chan *gorm.DB, 3) go func(){ queryResponses <- r.db.Raw("SELECT COUNT(*) FROM deposits").Scan(&users) }() go func(){ queryResponses <- r.db.Raw("SELECT COUNT(*) FROM services").Scan(&services) }() go func(){ queryResponses <- r.db.Raw("SELECT COUNT(*) FROM sells").Scan(&sells) }() for i := 0; i < 3; i += 1 { queryResponse := <-queryResponses if queryResponse.Error != nil { return nil, queryResponse.Error } } return &models.MyModel{ Users: users, Services: services, Sells: sells, }, nil }
接下来解决sqlmock测试的核心问题:协程执行顺序不确定,固定顺序的预期会因为调度差异导致误报。下面是具体的测试方案:
测试用例编写步骤
1. 初始化sqlmock与GORM实例
创建sqlmock连接,同时初始化GORM并关闭日志避免干扰测试:
func TestQueries(t *testing.T) { // 创建sqlmock连接 db, mock, err := sqlmock.New() if err != nil { t.Fatalf("创建sqlmock失败: %v", err) } defer db.Close() // 初始化GORM DB gormDB, err := gorm.Open(postgres.New(postgres.Config{ Conn: db, }), &gorm.Config{ Logger: logger.Default.LogMode(logger.Silent), }) if err != nil { t.Fatalf("创建gorm实例失败: %v", err) } repo := repository{db: gormDB} }
2. 设置无顺序依赖的查询预期
对三个COUNT查询分别设置预期,用正则匹配SQL(适配GORM可能的格式调整),不需要指定执行顺序:
// 模拟deposits表计数结果 mock.ExpectQuery(`SELECT COUNT\(\*\) FROM deposits`). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(100)) // 模拟services表计数结果 mock.ExpectQuery(`SELECT COUNT\(\*\) FROM services`). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(50)) // 模拟sells表计数结果 mock.ExpectQuery(`SELECT COUNT\(\*\) FROM sells`). WillReturnRows(sqlmock.NewRows([]string{"count"}).AddRow(200))
3. 执行函数并验证结果
调用目标函数,检查返回值是否符合预期,最后验证所有sqlmock预期都被执行:
result, err := repo.Queries(context.Background()) if err != nil { t.Fatalf("Queries执行失败: %v", err) } // 验证计数结果 if result.Users != 100 { t.Errorf("预期Users为100,实际得到%d", result.Users) } if result.Services != 50 { t.Errorf("预期Services为50,实际得到%d", result.Services) } if result.Sells != 200 { t.Errorf("预期Sells为200,实际得到%d", result.Sells) } // 验证所有查询预期都被触发 if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("存在未执行的查询预期: %v", err) }
常见问题处理
- 误报"query was not executed":检查正则表达式是否匹配GORM实际生成的SQL,可临时开启GORM日志查看真实执行语句。
- 协程阻塞:确保函数提前返回时(如查询出错),channel能被完全消费,你当前用的缓冲channel已避免此问题。
- 变量竞争:后续若涉及共享变量修改,需用
sync.Mutex保护。
内容的提问来源于stack exchange,提问作者Andre Luiz Aguero Domingos
相关产品推荐
相关产品推荐

