在Golang单元测试中使用数据库测试夹具的实例

259 阅读2分钟

你可以在你的Golang测试案例中使用这个简单的数据库测试夹具例子。你所需要的只是一个数据库实例和表截断功能,我为其创建了一个小包。

数据库辅助包

package test

import (
	"database/sql"
	"fmt"
)

func OpenDB(driver, address string, maxIdleConns int) *sql.DB {
	db, _ := sql.Open(driver, address)
	db.SetMaxIdleConns(maxIdleConns)

	return db
}

func CloseDB(db *sql.DB) {
	_ = db.Close()
}

func TruncateTables(db *sql.DB, tables []string) {
	_, _ = db.Exec("SET FOREIGN_KEY_CHECKS=0;")

	for _, v := range tables {
		_, _ = db.Exec(fmt.Sprintf("TRUNCATE TABLE %s;", v))
	}

	_, _ = db.Exec("SET FOREIGN_KEY_CHECKS=1;")
}

测试

假设我们在数据库中有一个叫做leagues 的表。我们想首先截断它,使它变得干净。然后,我们想用某些记录来填充它,以便我们的测试可以使用它。基本流程如下:

  1. 获得一个数据库实例。

  2. Truncate tableleagues.

  3. 用测试数据填充leagues

  4. 在每个测试前明确地调用loadFixtures 函数。

帮助文件

这是为前三个步骤准备的:

package league

import (
	"database/sql"
	"fmt"
	"os"
	"strings"
	"testing"

	"internal/pkg/test"
)

// -----------------------------------------------------------------------------

var DB *sql.DB

func TestMain(m *testing.M) {
	setup()
	code := m.Run()
	teardown()
	os.Exit(code)
}

func setup() {
	// Prepare database.
	dbAdr := "user:pass@tcp(0.0.0.0:3306)/football?charset=utf8mb4&collation=utf8mb4_unicode_ci"
	dbDrv := "mysql"
	dbIdl := 5

	// Obtain a database instance.
	DB = test.OpenDB(dbDrv, dbAdr, dbIdl)

	fmt.Printf("\033[1;36m%s\033[0m", "> Setup completed\n")
}

func teardown() {
	// Close the database instance.
	test.CloseDB(DB)

	fmt.Printf("\033[1;36m%s\033[0m", "> Teardown completed")
	fmt.Printf("\n")
}

// -----------------------------------------------------------------------------

func loadFixtures(db *sql.DB) {
	test.TruncateTables(db, []string{"leagues"})
	fmt.Printf("\033[1;36m%s\033[0m", "> Tables truncated\n")

	data := []struct {
		name              interface{}
		isActive          interface{}
		createdAt         interface{}
		deletedAt         interface{}
	}{
		{
			"La Liga",
			"true",
			"2019-12-31 23:59:59",
			"null",
		},
		{
			"Premier League",
			"true",
			"2019-12-31 23:59:59",
			"null",
		},
	}

	query := `
INSERT INTO leagues
(name, is_active, created_at, deleted_at)
VALUES
`

	for _, d := range data {
		query += fmt.Sprintf(
			"('%v', %v, '%v', %v),\n",
			d.name,
			d.isActive,
			d.createdAt,
			d.deletedAt,
		)
	}
	query = strings.TrimSuffix(query, ",\n")

	_, _ = db.Exec(query)

	fmt.Printf("\033[1;36m%s\033[0m", "> Fixtures loaded\n")
}

重要说明

根据你的测试,你极有可能得到一个类似于Error 1062: Duplicate entry '1' for key 'PRIMARY' 的错误。无论这种情况是否发生,你必须在test.TruncateTables() 行之前执行LOCK TABLES {your_table_name} WRITE; ,然后在UNLOCK TABLES;

测试文件

这是在最后一步,你的测试调用loadFixtures 函数:

package league

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestCreate(t *testing.T) {
	// DB is already ready for you!
	loadFixtures(DB)
	
	rq := httptest.NewRequest(http.MethodGet, "/leagues", nil)
	rw := httptest.NewRecorder()

	createHandler := NewCreate(DB)
	createHandler.Create(rw, rq)
	
	// Assert against rw.Body.String()
}

输出

正如你在下面看到的,我只有TestCreate 测试案例依赖于固定装置,而不是其他的:

$ go test ./... -v

> Setup completed
=== RUN   TestRetrieve
--- PASS: TestRetrieve (0.00s)
> Tables truncated
> Fixtures loaded
=== RUN   TestCreate
--- PASS: TestCreate (0.00s)
=== RUN   TestList
--- PASS: TestList (0.00s)
PASS
> Teardown completed
ok  	internal/league	0.051s