在 Go 中使用 dockertest 進行集成測試

爭做團隊核心程序員,關注「幽鬼」

進行集成測試(或系統測試)通常意味着讓數據庫填充數據,像 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/


歡迎關注「幽鬼」,像她一樣做團隊的核心。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/-nytxEAtHn9mDCq2IhmEoQ