在 Go 中使用 Dockertest 進(jìn)行集成測試
進(jìn)行集成測試(或系統(tǒng)測試)通常意味著讓數(shù)據(jù)庫填充數(shù)據(jù),像 redis、elasticsearch 等,通常,我們的軟件與之交互的任何基礎(chǔ)設(shè)施都可以工作。
最常見的方法是復(fù)制我們的生產(chǎn)基礎(chǔ)設(shè)施,其實用容器來實現(xiàn)還是比較容易的,比如 docker 容器。
我們可以為需要復(fù)制的每個服務(wù)設(shè)置和運行一個容器,我們可以使用 docker-compose 對其進(jìn)行編排并創(chuàng)建一些 makefile 或只是一個簡單的腳本來準(zhǔn)備基礎(chǔ)設(shè)施并運行集成測試。
如果你的測試是獨立的(它們應(yīng)該是獨立的),你必須找到在測試之間“重新啟動”基礎(chǔ)設(shè)施服務(wù)的方法,這可能很難通過分離的基礎(chǔ)設(shè)施設(shè)置和測試來實現(xiàn)(基礎(chǔ)設(shè)施是在腳本中設(shè)置的,而測試在 Go 文件中)
01 dockertest
如果你使用的是 Go,則可以使用 dockertest,一個可以管理和編排 Go 測試文件中的容器的庫。
從 Go 文件管理測試基礎(chǔ)設(shè)施容器,允許我們控制在每個測試中需要的服務(wù)(例如,某些包正在使用數(shù)據(jù)庫而不是 Redis,為這個測試運行 Redis 沒有意義)
安裝 dockertest
要安裝 dockertest,只需運行:
- go get -u github.com/ory/dockertest/v3
使用 dockertest
使用 dockertest 設(shè)置基礎(chǔ)設(shè)施的最簡單方法是在測試文件的TestMain 函數(shù)中添加設(shè)置代碼。
TestMain 是在包中運行測試之前調(diào)用的函數(shù),更多信息參考這里。
這是如何使用 dockertest 設(shè)置 MySQL 服務(wù)的示例:
- 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)
- }
填充數(shù)據(jù)庫
現(xiàn)在我們有工作的數(shù)據(jù)庫服務(wù),但這個數(shù)據(jù)庫是空的。dockertest 正在為容器使用通用 MySQL 映像,并且沒有與我們的應(yīng)用程序相關(guān)的任何內(nèi)容。
之前寫了一篇關(guān)于 數(shù)據(jù)庫遷移,在那篇文章中,我談到了 go-migrate,一個運行數(shù)據(jù)庫遷移的工具,那篇文章,我專注于作為 CLI 工具使用,現(xiàn)在將在我們的 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 數(shù)據(jù)庫后,遷移工具填充數(shù)據(jù)庫,我們的集成測試可以使用數(shù)據(jù)庫中的相同數(shù)據(jù)運行。
如果應(yīng)用程序有多個包(這是常見情況),我會將服務(wù)的設(shè)置代碼放在一個獨立文件中,該文件在每個包中調(diào)用:
- // 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) {
- ...
- }
- }
以這種方式在每個測試塊上執(zhí)行此操作,服務(wù)在新容器中運行,從而使測試完全獨立。
作為最后一個提示,我建議將集成測試放在不同的包中以避免循環(huán)導(dǎo)入。
原文鏈接:https://sergiocarracedo.es/integration-tests-in-golang-with-dockertest/
本文轉(zhuǎn)載自微信公眾號「幽鬼」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系幽鬼公眾號。