Go 教程:测试数据库交互
当遇到整合测试或者端到端测试的时候,我们可能无法对线上的每一个部分都很好的模拟,尤其是涉及到与数据库的交互时,这个问题就变得更加棘手了。
举个与数据库交互测试最简单的例子,服务器根据 id 返回正确的数据。要想完成这个测试,首先得保证测试数据库里有这条数据。
第一步,你要先检查数据库的连接状态,并且确保里面没有多余的数据。我们要保证测试数据的独立性,比如要测试记录分页,如果数据库里混有一些其他不相关的数据,就可能导致测试失败。
第二步,往数据库插入数据,可以通过执行 SQL 语句,也可以使用数据库迁移。
Go 是一个新语言。尽管它从现有的语言借鉴了很多思想,但是它也有自己独特的属性,正是这些特有的属性,使 Go 能够编写出更加高效的程序。
作为一个前 Ruby on Rails 开发者,我很欣赏 Rails 提供的用于自动化测试的 FactoryBot 和 DatabaseCleaner 等方案。而 Go 则是站在巨人的肩膀上,取其精华,并发扬光大。
我在这里分享一些支持数据库的 packages :
这个包基本上模仿了 “Rails’ way” 为数据库应用编写数据库测试,样本数据保存在 fixtures 文件中。 在执行每次测试之前。 都会清理测试的数据库并讲 fixture 文件中的数据加载到数据库中。
testfixtures 支持开箱即用。它会做两件事,在执行每次测试之前。 都会清理测试的数据库并讲 fixture 文件中的数据加载到数据库中。不支持并行。
2. dbcleaner
清理数据库以进行测试,灵感来自 Ruby 的 database_cleaner 。它用 flock 引擎实现系统调用来确保运行中支持并行运行而不会出现问题。
dbcleaner 允许每次使用时清除数据库。也允许并行测试。
3. go-txdb
txdb 包是一个独立的基于事务的 sql 数据库引擎。建立连接之后,就会开启事务,所有的操作都会在这个事务之内。如果有并发操作,则会开启锁机制,并自动中断连接。
我们可以利用 go-txdb 支持事务的特性来做测试。这也就意味着每个测试都可以运行在隔离的数据库实例中,并且可以同时进行多个测试。
4. polluter
这个包开发的目的主要就是服务于测试的,通过它可以使用简单的 .yaml 文件批量生成数据记录。
polluter 可以通过 YAML 文件批量生成数据。这个包开发的定位就是数据库清理工具或者是做为支持事物的数据库。
麻雀虽小,五脏俱全,数据库该有的功能,它都实现了。如果你想要一些随机数,就可以用一些生成假数据的包,比如 faker 来预先把数据预先生成在文件中。如果遇到外键约束混乱的问题,你也可以利用 polluter 遵从数据在文件中排列顺序的特点,按照正确的顺序调整数据的位置就好了。
为了更加方便地操作数据,建议你在测试的时候直接把主键 id 写死。如果想采用自增主键,可以通过设置初始值的方式来管理主键。例如,可以在 MySQL 中这样执行 ALTER TABLE table_name AUTO_INCREMENT = 10;
testfixtures 基本是开箱即用的,但是我更倾向于根据不同的需求采用不同的配置方案:
- 对于验收测试 — database cleaner 搭配 polluter
- 对于集成测试 — go-txdb 搭配 polluter.
package main
import (
"database/sql"
"fmt"
"os"
"testing"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/romanyx/polluter"
txdb "github.com/DATA-DOG/go-txdb"
)
func init() {
txdb.Register("mysqltx", "mysql", "test:test@tcp(localhost:3306)/test")
}
func exampleSuite(t *testing.T) func() error {
db, cleanup := prepareMySQLDB(t)
seed, err := os.Open("seed.yaml")
if err != nil {
t.Fatalf("failed to open seed file: %s", err)
}
defer seed.Close()
p := polluter.New(polluter.MySQLEngine(db))
if err := p.Pollute(seed); err != nil {
t.Fatalf("failed to pollute: %s", err)
}
return cleanup
}
func TestExample(t *testing.T) {
t.Parallel()
defer exampleSuite(t)()
...
}
func prepareMySQLDB(t *testing.T) (db *sql.DB, cleanup func() error) {
cName := fmt.Sprintf("connection_%d", time.Now().UnixNano())
db, err := sql.Open("mysqltx", cName)
if err != nil {
t.Fatalf("open mysqltx connection: %s", err)
}
return db, db.Close
}
这段代码展示了 polluter 和 go-txdb. 结合使用的效果。
每个测试都有根据自身需求批量生成的数据,每个测试都运行在隔离的事务中,因此,每个测试都是独立的,不论执行的顺序如何,后面的测试都不会因为前面测试的失败而受到影响。这样就可以同时运行所有的测试,也不必担心会有竞争问题。
这是到目前为止我发现的最好的数据库交互测试方案,希望对你有用。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
推荐文章: