Golang 如何测试数据库事务_Golang 事务回滚与提交行为验证

使用内存数据库或sqlmock测试Go事务。1. 用SQLite测试回滚:插入数据后调用Rollback,验证数据未保存;2. 测试Commit后数据持久化;3. 模拟错误触发Rollback,确保无脏数据;4. 用sqlmock模拟事务流程,验证执行顺序。

在 Go 语言中测试数据库事务的关键在于验证事务的提交与回滚行为是否符合预期。我们通常使用 sql.Tx 来管理事务,并借助测试工具模拟真实场景,确保数据一致性。下面介绍如何有效测试事务中的回滚和提交逻辑。

使用内存数据库进行事务测试

推荐使用支持事务的内存数据库(如 SQLite)来加速测试并避免依赖外部环境。

注意:MySQL 和 PostgreSQL 的驱动也可以配合 Docker 容器使用,但 SQLite 更轻量,适合单元测试。

示例代码:

package main

import (
    "database/sql"
    "log"
    "testing"

    _ "github.com/mattn/go-sqlite3"
)

func setupTestDB() *sql.DB {
    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        log.Fatal(err)
    }

    _, err = db.Exec(`CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)`)
    if err != nil {
        log.Fatal(err)
    }

    return db
}

func insertUser(tx *sql.Tx, name string) error {
    _, err := tx.Exec("INSERT INTO users (name) VALUES (?)", name)
    return err
}

编写测试函数验证回滚:

func TestTransaction_Rollback(t *testing.T) {
    db := setupTestDB()
    defer db.Close()

    tx, _ := db.Begin()

    err := insertUser(tx, "Alice")
    if err != nil {
        t.Fatal(err)
    }

    // 不调用 Commit,直接 Rollback
    tx.Rollback()

    var count int
    err = db.QueryRow("SELECT COUNT(*) FROM users WHERE name = ?", "Alice").Scan(&count)
    if err != nil {
        t.Fatal(err)
    }

    if count != 0 {
        t.Errorf("expected no user after rollback, but got %d", count)
    }
}

验证事务提交是否生效

测试提交后数据应持久化到数据库。

func TestTransaction_Commit(t *testing.T) {
    db := setupTestDB()
    defer db.Close()

    tx, _ := db.Begin()

    err := insertUser(tx, "Bob")
    if err != nil {
        t.Fatal(err)
    }

    err = tx.Commit()
    if err != nil {
        t.Fatal(err)
    }

    var count int
    err = db.QueryRow("SELECT COUNT(*) FROM users WHERE name = ?", "Bob").Scan(&count)
    if err != nil {
        t.Fatal(err)
    }

    if count != 1 {
        t.Errorf("expected 1 user after commit, but got %d", count)
    }
}

模拟错误触发自动回滚

实际业务中常因操作失败需回滚事务。可在插入非法数据时测试自动回滚行为。

例如:插入违反约束的数据(如空名称),然后回滚。

func TestTransaction_ErrorHandling(t *testing.T) {
    db := setupTestDB()
    defer db.Close()

    tx, _ := db.Begin()

    err := insertUser(tx, "")
    if err != nil {
        tx.Rollback() // 显式回滚
    } else {
        tx.Commit()
    }

    var count int
    err = db.QueryRow("SELECT COUNT(*) FROM users WHERE name = ''").Scan(&count)
    if err != nil {
        t.Fatal(err)
    }

    if count != 0 {
        t.Errorf("expected no empty-name user after rollback, but got %d", count)
    }
}

这种写法能确保即使插入失败,也不会留下脏数据。

使用 sqlmock 模拟事务流程(高级测试)

对于不希望真正连接数据库的场景,可用 sqlmock 库模拟事务行为。

安装:

go get github.com/DATA-DOG/go-sqlmock

示例测试:

import (
    "testing"
    "github.com/DATA-DOG/go-sqlmock"
)

func TestWithSqlMock(t *testing.T) {
    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("failed to open sqlmock: %v", err)
    }
    defer db.Close()

    mock.ExpectBegin()
    mock.ExpectExec("INSERT INTO users").WillReturnResult(sqlmock.NewResult(1, 1))
    mock.ExpectRollback() // 假设我们回滚

    tx, _ := db.Begin()
    tx.Exec("INSERT INTO users (name) VALUES (?)", "Charlie")
    tx.Rollback()

    if err := mock.ExpectationsWereMet(); err != nil {
        t.Errorf("unfulfilled expectations: %s", err)
    }
}

这种方式适合集成测试或服务层逻辑验证,无需真实数据库。

基本上就这些。通过内存数据库或 mock 工具,可以完整覆盖事务的提交、回滚和异常处理路径,保证业务逻辑正确性。