在这篇文章中,我们介绍一下如何利用 Go
强大的 STL
、Docker
和 SQL 事务
对 PostgreSQL
存储库进行并行测试。
真实数据库与模拟测试的对比
拥有容器之前,我们很难做到使用真正的技术环境来代替模拟。对于数据库来说,通常每台机器上只有一个数据库服务器,每个应用程序单元上也只有一个数据库。
对于数据库交互的测试,模拟是目前最为普遍接受的一种解决方案。Mocks
可以帮助解决一些边缘情况,例如在执行过程中丢失连接等。无论是需要测试连接还是测试查询,来确保它们可以在真实的数据库中按照预期工作,我们都可以选择使用Docker
,轻松的开始针对真实数据库的测试。
针对真实数据库进行测试的优缺点
优点:
- 验证查询,可以确保在生产环境不出现意外情况
- 识别可能出现的误差
- 组合成复杂的场景,以确保我们的约束可以按照预期工作
缺点:
- 管理数据库生命周期会产生一定的高成本
测试方法以及过程
测试思想
将每个包都视为一个代码单元,在项目中测试一个代码单元时,最终目的是确保它在生产中工作。
所需工具
- Docker
- go
在深入研究代码之前,我假设您已经熟悉 Go 及其强大的标准库。 这是一个示例存储库,您可以在其中找到并运行本文中介绍的测试。
基本设置
在运行包测试之前,必须实施对PostgreSQL
数据库的设置。
package psql_test
import (
"fmt"
"github.com/adrianbrad/psqldocker"
"github.com/adrianbrad/psqltest"
"log"
"os"
"testing"
)
func TestMain(m *testing.M) {
// psql connection parameters.
const (
usr = "usr"
password = "pass"
dbName = "tst"
containerName = "psql_docker_tests"
)
// run a new psql docker container.
c, err := psqldocker.NewContainer(
usr,
password,
dbName,
psqldocker.WithContainerName(containerName),
psqldocker.WithSql(`
CREATE TABLE users(
user_id UUID PRIMARY KEY,
email VARCHAR NOT NULL
);`),
)
if err != nil {
log.Fatalf("err while creating new psql container: %s", err)
}
// exit code
var ret int
defer func() {
// close the psql container
err = c.Close()
if err != nil {
log.Printf("err while tearing down db container: %s", err)
}
// exit with the code provided by executing m.Run().
os.Exit(ret)
}()
// compose the psql dsn.
dsn := fmt.Sprintf(
"user=%s "+
"password=%s "+
"dbname=%s "+
"host=localhost "+
"port=%s "+
"sslmode=disable",
usr,
password,
dbName,
c.Port(),
)
// register the psql container connection details
// in order to be able to spawn new database connections
// in an isolated transaction.
psqltest.Register(dsn)
// run the package tests.
ret = m.Run()
}
这是在 TestMain
函数和以下外部包的帮助下实现的: psqldocker
+ psqltest
-
TestMain
允许开发人员在运行包测试之前或者之后,都可以运行任意代码。 -
psqldocker
以编程方式来管理PostgreSQL Docker
容器的生命周期。 使用psqldocker.NewContainer()
创建一个新的PostgreSQL Docker
容器。在使用m.Run()
运行包测试后容器停止。 -
psqltest
是一组PostgreSQL
测试实用程序,类似于net/http
的/net/http/httptest
。psqltest.Register()
函数是DATA-DOG/go-txdb/db.go:Register()
的包装器,它注册了一个新的SQL驱动程序
,当使用它打开数据库连接时,将在隔离的SQL事务
中启动该连接。
测试的实施
代码实现:
import (
"testing"
"github.com/adrianbrad/psqltest"
)
func TestUserRepository(t *testing.T) {
t.Run("CreateUser", func(t *testing.T) {
t.Parallel()
t.Run("Success", func(t *testing.T) {
t.Parallel()
db := psqltest.NewTransactionTestingDB(t)
...
})
t.Run("InvalidID", func(t *testing.T) {
t.Parallel()
db := psqltest.NewTransactionTestingDB(t)
...
})
})
t.Run("GetUser", func(t *testing.T) {
t.Parallel()
t.Run("Success", func(t *testing.T) {
t.Parallel()
db := psqltest.NewTransactionTestingDB(t)
...
})
t.Run("NotFound", func(t *testing.T) {
t.Parallel()
db := psqltest.NewTransactionTestingDB(t)
...
})
})
}
从代码中可以看出,每个测试都在单独的SQL事务
中打开一个新的数据库连接,因此并行测试执行是安全的。这使得开发人员能够在多个测试中使用相同的数据库实体,例如我们可以在多个可以改变实体状态的测试中,使用相同的User
实体。
在之前TestMain
中,我们利用psqltest.Register()
注册了驱动程序,现在我们通过psqltest.NewTransactionTestingDB()
来创建一个新的数据库连接。
sql.Open()
的DSN
(第二个输入参数)传入的是测试名称,它充当的是底层 SQL
事务的标识符。
使用同一个 DSN
,开发人员可以打开多个数据库连接到同一个事务。例如,第一个测试中的标识符是TestUserRepository/CreateUser/Success
psqltest.NewTransactionTestingDB()
实现代码
const driver = "pgsqltx"
// NewTransactionTestingDB returns a new transaction DB connection.
// It acts as a test helper and also takes care of closing the DB connection.
func NewTransactionTestingDB(t *testing.T) *sql.DB {
// mark this function as a test helper
t.Helper()
// open a new database connection in a SQL transaction.
db, err := sql.Open(driver, t.Name())
if err != nil {
t.Fatalf("open txdb conn: %s", err)
}
// ping the database to ensure connection validity.
err = db.Ping()
if err != nil {
t.Fatalf("ping: %s", err)
}
// register a cleanup function that closes the database
// connection thus reverting the transaction.
t.Cleanup(func() {
_ = db.Close()
})
return db
}
测试运行及结果
包中的测试
./internal/psql
都是针对真实的 PostgreSQL
数据库并行执行的。
这里的主要内容是执行时间,大约为4
秒。执行时间包括使用 go
以编程方式启动和销毁一个新的 PostgreSQL
数据库。
总结
在本文中,主要向大家展示了如何使用 Docker
以编程方式启动
和拆卸
PostgreSQL
数据库。以及如何在单独的 SQL
事务中打开数据库连接,来实现对数据库实例执行的安全并行测试。
如果你想了解更多详细信息,可以移步:adrianbrad.medium.com/parallel-po…