You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 23:40:36