Go语言:如何使用testcontainers构建数据访问层的集成测试?

如何确保数据访问层中的SQL语句编写正确?即使在数据库客户端中手动测试通过的SQL语句,放到程序代码中也不能保证一定能正常工作。

问题示例

让我们看一个查询tier=2的客户记录的SQL语句示例:

复制
SELECT id, name, tier, created_at, updated_at FROM customer WHERE tier = 21.2.3.

然而,当这条SQL语句被放入程序代码中时可能会出现问题:

复制
// GO // connect to database db, err := sql.Open("mysql", <<database connection string>>) // execute query rows, err := db.Query("SELECT id, name, tier," + "created_at, updated_at"+"FROM customer WHERE tier=?", tier)1.2.3.4.5.

你能发现问题所在吗?是的,在update_at前的换行处缺少了一个空格。这是在将SQL语句放入程序代码时常见的粗心错误。肉眼检查可能不容易发现这个问题,而合适的自动化测试才是确保程序代码按预期工作的可靠方式。

使用容器进行测试

启动真实数据库进行测试是最佳方法。得益于Docker容器的普及,自动化测试可以在容器中启动数据库进行测试,测试完成后直接销毁。

在Java中,使用库方法调用和JUnit可以轻松实现与数据库的集成测试。那么在GO中是否也可以构建相同的自动化测试呢?

好消息是testcontainers库也支持Go。本文将深入探讨如何使用Go构建集成测试。

数据访问层

为了实现关注点分离和便于维护,将SQL语句混入业务逻辑通常不是一个好主意。最佳实践是将它们封装在一个称为数据访问对象(DAO)的独立组件中,该组件充当业务逻辑和数据库之间的适配器。

使用MySQL测试容器

首先让我们看看如何启动测试容器的基本操作。

需要导入testcontainers库的以下两个包:

复制
import ( "github.com/testcontainers/testcontainers-go" "github.com/testcontainers/testcontainers-go/modules/mysql" )1.2.3.4.

使用mysql.Run()并传入以下参数来启动MySQL容器:

MySQL镜像版本数据库名称用户名和密码初始数据库脚本
复制
ctx := context.Background() mysqlContainer, err := mysql.Run(ctx, "mysql:8.0.36", mysql.WithDatabase("example"), mysql.WithUsername("appuser"), mysql.WithPassword("passme"), mysql.WithScripts(filepath.Join("testdata", "schema.sql")), ) if err != nil { log.Panicf("failed to start container: %s\n", err) }1.2.3.4.5.6.7.8.9.10.11.

连接MySQL容器

要连接MySQL数据库,需要导入Go的标准库database/sql和MySQL驱动。为避免与testcontainer库的MySQL模块名称冲突,给驱动包添加下划线别名:

复制
import ( _ "github.com/go-sql-driver/mysql" "database/sql" )1.2.3.4.

与Java类似,Go中的数据库连接也是通过连接字符串实现。testcontainers库提供了一个便捷函数mysqlContainer.ConnectionString(),它可以生成格式正确的连接字符串。

测试套件实现

首先,定义一个包含客户DAO、MySQL容器和数据库连接引用的测试套件结构体。suite.Suite提供了测试框架的元素,如用于管理测试状态和支持测试日志的testing.T。

复制
type CustomerDaoTestSuite struct { suite.Suite dao *dao.CustomerDao mysqlContainer *mysql.MySQLContainer db *sql.DB }1.2.3.4.5.6.

生命周期函数

Testify框架提供了以下接口来实现测试生命周期:

SetupSuite() - 在所有测试场景执行前调用一次(相当于JUnit中的@BeforeAll)SetupTest() - 每个测试场景前的设置函数(相当于JUnit中的@BeforeEach)TearDownTest() - 每个测试场景完成后的清理函数(相当于JUnit中的@AfterEach)TearDownSuite() - 在所有测试场景结束后调用一次(相当于JUnit中的@AfterAll)

最终思考

数据访问层的集成测试至关重要。即使SQL语句在数据库客户端中运行完美,放入程序代码中也不能保证正常工作。使用真实数据库进行测试是确认组件正常运行的唯一方法。

借助testcontainers库和Testify框架,集成测试可以自动启动容器中的数据库,并在完整的测试生命周期中将DAO连接到数据库进行测试,就像Java中的JUnit一样。该库对开发人员友好,只需几个函数调用就能轻松完成测试设置。

阅读剩余
THE END