进行集成测试(或系统测试)通常意味着让数据库填充数据,像 redis、elasticsearch 等,通常,我们的软件与之交互的任何基础设施都可以工作。
最常见的方法是复制我们的生产基础设施,其实用容器来实现还是比较容易的,比如 docker 容器。
我们可以为需要复制的每个服务设置和运行一个容器,我们可以使用 docker-compose 对其进行编排并创建一些 makefile 或只是一个简单的脚本来准备基础设施并运行集成测试。
如果你的测试是独立的(它们应该是独立的),你必须找到在测试之间“重新启动”基础设施服务的方法,这可能很难通过分离的基础设施设置和测试来实现(基础设施是在脚本中设置的,而测试在 Go 文件中)
01 dockertest
如果你使用的是 Go,则可以使用 dockertest,一个可以管理和编排 Go 测试文件中的容器的库。
从 Go 文件管理测试基础设施容器,允许我们控制在每个测试中需要的服务(例如,某些包正在使用数据库而不是 Redis,为这个测试运行 Redis 没有意义)
安装 dockertest
要安装 dockertest,只需运行:
- go get -u github.com/ory/dockertest/v3
使用 dockertest
使用 dockertest 设置基础设施的最简单方法是在测试文件的TestMain 函数中添加设置代码。
TestMain 是在包中运行测试之前调用的函数,更多信息参考这里。
这是如何使用 dockertest 设置 MySQL 服务的示例:
- package mypackage_test
- import (
- "database/sql"
- "fmt"
- "log"
- "os"
- "testing"
- _ "github.com/go-sql-driver/mysql"
- "github.com/ory/dockertest/v3"
- )
- var db *sql.DB
- func TestMain(m *testing.M) {
- // uses a sensible default on windows (tcp/http) and linux/osx (socket)
- pool, err := dockertest.NewPool("")
- if err != nil {
- log.Fatalf("Could not connect to docker: %s", err)
- }
- // pulls an image, creates a container based on it and runs it
- resource, err := pool.Run("mysql", "5.7", []string{"MYSQL_ROOT_PASSWORD=secret"})
- if err != nil {
- log.Fatalf("Could not start resource: %s", err)
- }
- // exponential backoff-retry, because the application in the container might not be ready to accept connections yet
- if err := pool.Retry(func() error {
- var err error
- db, err = sql.Open("mysql", fmt.Sprintf("root:secret@(localhost:%s)/mysql", resource.GetPort("3306/tcp")))
- if err != nil {
- return err
- }
- return db.Ping()
- }); err != nil {
- log.Fatalf("Could not connect to docker: %s", err)
- }
- // RESERVED FOR DATABASE MIGRATIONS
- code := m.Run()
- // You can't defer this because os.Exit doesn't care for defer
- if err := pool.Purge(resource); err != nil {
- log.Fatalf("Could not purge resource: %s", err)
- }
- os.Exit(code)
- }
填充数据库
现在我们有工作的数据库服务,但这个数据库是空的。dockertest 正在为容器使用通用 MySQL 映像,并且没有与我们的应用程序相关的任何内容。
之前写了一篇关于 数据库迁移,在那篇文章中,我谈到了 go-migrate,一个运行数据库迁移的工具,那篇文章,我专注于作为 CLI 工具使用,现在将在我们的 Go 代码中使用它。
我们将先前编写的代码 // RESERVED FOR DATABASE MIGRATIONS 添加到此代码中:
- m, err := migrate.NewWithDatabaseInstance("file://<path-to-migration-folder>, "mysql", driver)
- if err != nil {
- log.Fatalf("Error running migrations: %s", err)
- }
- err = m.Up()
- if err != nil {
- log.Fatal(err.Error())
- }
然后在 dockertest up 数据库后,迁移工具填充数据库,我们的集成测试可以使用数据库中的相同数据运行。
如果应用程序有多个包(这是常见情况),我会将服务的设置代码放在一个独立文件中,该文件在每个包中调用:
- // it_utils.go
- package it_utils
- func IntegrationTestSetup() (*dockertest.Pool, *[]dockertestResource {
- // Setup the services
- //return the pool and the resources
- }
- func IntegrationTestTeardown(pool *dockertest.Pool, resources []*dockertest.Resource) {
- for _, resource := range resources {
- if err := pool.Purge(resource); err != nil {
- fmt.Printf("Could not purge resource: %s\n", err)
- }
- }
- }
那么在每个包的测试中我们只需要添加如下代码:
- package my_package
- func TestMyTests (t *testing.T) {
- if testing.Short() {
- t.Skip()
- }
- pool, resources := itutils.IntegrationTestSetup()
- defer itutils.IntegrationTestTeardown(pool, resources)
- t.Run("your test", func(t *testing.T) {
- ...
- }
- }
- func TestOtherTests (t *testing.T) {
- if testing.Short() {
- t.Skip()
- }
- pool, resources := itutils.IntegrationTestSetup()
- defer itutils.IntegrationTestTeardown(pool, resources)
- t.Run("your other test", func(t *testing.T) {
- ...
- }
- }
以这种方式在每个测试块上执行此操作,服务在新容器中运行,从而使测试完全独立。
作为最后一个提示,我建议将集成测试放在不同的包中以避免循环导入。
原文链接:https://sergiocarracedo.es/integration-tests-in-golang-with-dockertest/
本文转载自微信公众号「幽鬼」,可以通过以下二维码关注。转载本文请联系幽鬼公众号。