Golang使用Docker進(jìn)行集成測試的示例詳解
實(shí)踐表明,有時(shí)程序中某個(gè)模塊雖然可以單獨(dú)工作,但是并不能保證多個(gè)模塊組裝起來也可以同時(shí)工作,于是就有了集成測試。
集成測試需要解決外部依賴問題,如 MySQL、Redis、網(wǎng)絡(luò)等依賴,解決這些外部依賴問題最佳實(shí)踐則是使用 Docker,本文就來聊聊 Go 程序如何使用 Docker 來解決集成測試中外部依賴問題。
登錄程序示例
在 Web 開發(fā)中,登錄需求是一個(gè)較為常見的功能。所以,本文就以登錄程序?yàn)槔?,講解使用 Docker 啟動(dòng) Redis 進(jìn)行集成測試。
登錄程序如下:
func Login(mobile, smsCode string, rdb *redis.Client) (string, error) { ctx := context.Background() // 查找驗(yàn)證碼 captcha, err := GetSmsCaptchaFromRedis(ctx, rdb, mobile) if err != nil { if err == redis.Nil { return "", fmt.Errorf("invalid sms code or expired") } return "", err } if captcha != smsCode { return "", fmt.Errorf("invalid sms code") } token, _ := GenerateToken(32) err = SetAuthTokenToRedis(ctx, rdb, token, mobile) if err != nil { return "", err } return token, nil }
可以通過如下方式獲取 Redis 客戶端對象 rdb
:
import "github.com/redis/go-redis/v9" func NewRedisClient() *redis.Client { return redis.NewClient(&redis.Options{ Addr: "localhost:6379", }) }
生成隨機(jī) token
的函數(shù)定義如下:
var GenerateToken = func(length int) (string, error) { token := make([]byte, length) _, err := rand.Read(token) if err != nil { return "", err } return base64.URLEncoding.EncodeToString(token)[:length], nil }
本程序提供了如下幾個(gè)操作 Reids 的函數(shù):
var ( smsCaptchaExpire = 5 * time.Minute smsCaptchaKeyPrefix = "sms:captcha:%s" authTokenExpire = 24 * time.Hour authTokenKeyPrefix = "auth:token:%s" ) func SetSmsCaptchaToRedis(ctx context.Context, redis *redis.Client, mobile, captcha string) error { key := fmt.Sprintf(smsCaptchaKeyPrefix, mobile) return redis.Set(ctx, key, captcha, smsCaptchaExpire).Err() } func GetSmsCaptchaFromRedis(ctx context.Context, redis *redis.Client, mobile string) (string, error) { key := fmt.Sprintf(smsCaptchaKeyPrefix, mobile) return redis.Get(ctx, key).Result() } func DeleteSmsCaptchaFromRedis(ctx context.Context, redis *redis.Client, mobile string) error { key := fmt.Sprintf(smsCaptchaKeyPrefix, mobile) return redis.Del(ctx, key).Err() } func SetAuthTokenToRedis(ctx context.Context, redis *redis.Client, token, mobile string) error { key := fmt.Sprintf(authTokenKeyPrefix, token) return redis.Set(ctx, key, mobile, authTokenExpire).Err() } func GetAuthTokenFromRedis(ctx context.Context, redis *redis.Client, token string) (string, error) { key := fmt.Sprintf(authTokenKeyPrefix, token) return redis.Get(ctx, key).Result() } func DeleteAuthTokenFromRedis(ctx context.Context, redis *redis.Client, token string) error { key := fmt.Sprintf(authTokenKeyPrefix, token) return redis.Del(ctx, key).Err() }
Login
函數(shù)用法如下:
func main() { rdb := NewRedisClient() token, err := Login("13800001111", "123456", rdb) if err != nil { fmt.Println(err) return } fmt.Println(token) }
使用 Docker 進(jìn)行集成測試
要想對 Login
函數(shù)進(jìn)行集成測試,就需要解決 Reids 外部依賴問題。
在 Go 程序中,我們可以使用 testcontainers-go
這個(gè)包來解決,它可以讓我們很方便的在 Docker 中啟動(dòng) Reids 服務(wù)。
安裝 testcontainers-go
:
$ go get github.com/testcontainers/testcontainers-go
我們可以在測試代碼開始執(zhí)行之前啟動(dòng) Docker 容器來運(yùn)行 Redis 服務(wù),然后執(zhí)行測試代碼,最后測試代碼執(zhí)行完成后再停止并刪除 Docker 容器。
可以定義一個(gè) setup
函數(shù)用來準(zhǔn)備 Docker 容器:
var rdbClient *redis.Client func setup() func() { ctx := context.Background() req := testcontainers.ContainerRequest{ Image: "redis:6.0.20-alpine", ExposedPorts: []string{"6379/tcp"}, WaitingFor: wait.ForLog("Ready to accept connections"), } redisC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ ContainerRequest: req, Started: true, }) if err != nil { panic(fmt.Sprintf("failed to start container: %s", err.Error())) } endpoint, err := redisC.Endpoint(ctx, "") if err != nil { panic(fmt.Sprintf("failed to get endpoint: %s", err.Error())) } rdbClient = redis.NewClient(&redis.Options{ Addr: endpoint, }) // 清理 Redis 容器 return func() { if err := redisC.Terminate(ctx); err != nil { panic(fmt.Sprintf("failed to terminate container: %s", err.Error())) } } }
可以發(fā)現(xiàn)使用 testcontainers-go
啟動(dòng)一個(gè) Redis 容器非常簡單,我們指定了 Docker 容器鏡像為 redis:6.0.20-alpine
,映射端口為 6379/tcp
。
Redis 容器啟動(dòng)后將實(shí)例化的 Redis 客戶端保存到全局變量 rdbClient
中,方便在測試函數(shù)中使用,setup
函數(shù)最終返回一個(gè) teardown
函數(shù)可以清理容器。
定義 TestMain
函數(shù)如下,作為測試程序的入口:
func TestMain(m *testing.M) { teardown := setup() code := m.Run() teardown() os.Exit(code) }
為了測試 Login
函數(shù),我們需要在 Reids 中準(zhǔn)備一些測試數(shù)據(jù),因?yàn)?Login
函數(shù)內(nèi)部需要查詢 Reids 中的驗(yàn)證碼,所以可以定義一個(gè) setupLogin
函數(shù)來實(shí)現(xiàn):
func setupLogin(tb testing.TB) func(tb testing.TB) { // 準(zhǔn)備測試數(shù)據(jù) err := SetSmsCaptchaToRedis(context.Background(), rdbClient, "18900001111", "123456") assert.NoError(tb, err) // 清理測試數(shù)據(jù) return func(tb testing.TB) { err := DeleteSmsCaptchaFromRedis(context.Background(), rdbClient, "18900001111") assert.NoError(tb, err) err = DeleteAuthTokenFromRedis(context.Background(), rdbClient, "token") assert.NoError(tb, err) } }
setupLogin
函數(shù)返回 teardownLogin
函數(shù)用來清理 Redis 中的測試數(shù)據(jù),防止當(dāng)有多個(gè)測試函數(shù)時(shí)互相影響。
現(xiàn)在可以編寫 Login
函數(shù)的測試代碼了:
func TestLogin(t *testing.T) { teardownLogin := setupLogin(t) defer teardownLogin(t) // 測試登錄成功情況 token, err := Login("18900001111", "123456", rdbClient) assert.NoError(t, err) assert.Equal(t, "token", token) // 檢查 Redis 中是否存在 token mobile, err := GetAuthTokenFromRedis(context.Background(), rdbClient, "token") assert.NoError(t, err) assert.Equal(t, "18900001111", mobile) }
TestLogin
函數(shù)非常簡單,這得益于前期的準(zhǔn)備工作做的非常全面。
使用 go test
來執(zhí)行測試函數(shù):
$ go test -v
2023/07/26 20:48:12 github.com/testcontainers/testcontainers-go - Connected to docker:
Server Version: 20.10.21
API Version: 1.41
Operating System: Docker Desktop
Total Memory: 7851 MB
2023/07/26 20:48:12 ?? Creating container for image docker.io/testcontainers/ryuk:0.5.1
2023/07/26 20:48:12 ? Container created: a261dc723001
2023/07/26 20:48:12 ?? Starting container: a261dc723001
2023/07/26 20:48:12 ? Container started: a261dc723001
2023/07/26 20:48:12 ?? Waiting for container id a261dc723001 image: docker.io/testcontainers/ryuk:0.5.1. Waiting for: &{Port:8080/tcp timeout:<nil> PollInterval:100ms}
2023/07/26 20:48:13 ?? Creating container for image redis:6.0.20-alpine
2023/07/26 20:48:13 ? Container created: 6420ead815a0
2023/07/26 20:48:13 ?? Starting container: 6420ead815a0
2023/07/26 20:48:13 ? Container started: 6420ead815a0
2023/07/26 20:48:13 ?? Waiting for container id 6420ead815a0 image: redis:6.0.20-alpine. Waiting for: &{timeout:<nil> Log:Ready to accept connections Occurrence:1 PollInterval:100ms}
=== RUN TestLogin
--- PASS: TestLogin (0.01s)
PASS
2023/07/26 20:48:13 ?? Terminating container: 6420ead815a0
2023/07/26 20:48:13 ?? Container terminated: 6420ead815a0
ok github.com/jianghushinian/test/db/redis 1.630s
測試通過。
總結(jié)
我們使用 testcontainers-go
包實(shí)現(xiàn)了在 Go 程序中啟動(dòng)一個(gè) Docker 容器,以此解決了集成測試中依賴外部 Redis 問題。
可以發(fā)現(xiàn),Docker 非常適合集成測試,使用 Dokcer 來輔助集成測試是 Go 應(yīng)用程序集成測試的最佳實(shí)踐。而完善的集成測試,可以確保應(yīng)用程序的可靠性、可擴(kuò)展性和可維護(hù)性。
以上就是Golang使用Docker進(jìn)行集成測試的示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Go Docker集成測試的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類型操作
這篇文章主要介紹了golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類型操作,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12詳解Golang中NewTimer計(jì)時(shí)器的底層實(shí)現(xiàn)原理
本文將主要介紹一下Go語言中的NewTimer,首先展示基于NewTimer創(chuàng)建的定時(shí)器來實(shí)現(xiàn)超時(shí)控制。接著通過一系列問題的跟進(jìn),展示了NewTimer的底層實(shí)現(xiàn)原理,需要的可以參考一下2023-05-05