如何确保数据访问层中的SQL语句编写正确?即使在数据库客户端中手动测试通过的SQL语句,放到程序代码中也不能保证一定能正常工作。
问题示例
让我们看一个查询tier=2的客户记录的SQL语句示例:
SELECT id, name, tier, created_at, updated_at
FROM customer
WHERE tier = 2
然而,当这条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)
你能发现问题所在吗?是的,在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"
)
使用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)
}
连接MySQL容器
要连接MySQL数据库,需要导入Go的标准库database/sql和MySQL驱动。为避免与testcontainer库的MySQL模块名称冲突,给驱动包添加下划线别名:
import (
_ "github.com/go-sql-driver/mysql"
"database/sql"
)
与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
}
生命周期函数
Testify框架提供了以下接口来实现测试生命周期:
- SetupSuite() - 在所有测试场景执行前调用一次(相当于JUnit中的@BeforeAll)
- SetupTest() - 每个测试场景前的设置函数(相当于JUnit中的@BeforeEach)
- TearDownTest() - 每个测试场景完成后的清理函数(相当于JUnit中的@AfterEach)
- TearDownSuite() - 在所有测试场景结束后调用一次(相当于JUnit中的@AfterAll)
最终思考
数据访问层的集成测试至关重要。即使SQL语句在数据库客户端中运行完美,放入程序代码中也不能保证正常工作。使用真实数据库进行测试是确认组件正常运行的唯一方法。
借助testcontainers库和Testify框架,集成测试可以自动启动容器中的数据库,并在完整的测试生命周期中将DAO连接到数据库进行测试,就像Java中的JUnit一样。该库对开发人员友好,只需几个函数调用就能轻松完成测试设置。