Go語言:如何使用testcontainers構(gòu)建數(shù)據(jù)訪問層的集成測試?
如何確保數(shù)據(jù)訪問層中的SQL語句編寫正確?即使在數(shù)據(jù)庫客戶端中手動測試通過的SQL語句,放到程序代碼中也不能保證一定能正常工作。
問題示例
讓我們看一個查詢tier=2的客戶記錄的SQL語句示例:
SELECT id, name, tier, created_at, updated_at
FROM customer
WHERE tier = 2
然而,當(dāng)這條SQL語句被放入程序代碼中時可能會出現(xiàn)問題:
// GO
// connect to database
db, err := sql.Open("mysql", <<database connection string>>)
// execute query
rows, err := db.Query("SELECT id, name, tier," + "created_at, updated_at"+"FROM customer WHERE tier=?", tier)
你能發(fā)現(xiàn)問題所在嗎?是的,在update_at前的換行處缺少了一個空格。這是在將SQL語句放入程序代碼時常見的粗心錯誤。肉眼檢查可能不容易發(fā)現(xiàn)這個問題,而合適的自動化測試才是確保程序代碼按預(yù)期工作的可靠方式。
使用容器進(jìn)行測試
啟動真實數(shù)據(jù)庫進(jìn)行測試是最佳方法。得益于Docker容器的普及,自動化測試可以在容器中啟動數(shù)據(jù)庫進(jìn)行測試,測試完成后直接銷毀。
在Java中,使用庫方法調(diào)用和JUnit可以輕松實現(xiàn)與數(shù)據(jù)庫的集成測試。那么在GO中是否也可以構(gòu)建相同的自動化測試呢?
好消息是testcontainers庫也支持Go。本文將深入探討如何使用Go構(gòu)建集成測試。
數(shù)據(jù)訪問層
為了實現(xiàn)關(guān)注點分離和便于維護(hù),將SQL語句混入業(yè)務(wù)邏輯通常不是一個好主意。最佳實踐是將它們封裝在一個稱為數(shù)據(jù)訪問對象(DAO)的獨(dú)立組件中,該組件充當(dāng)業(yè)務(wù)邏輯和數(shù)據(jù)庫之間的適配器。
使用MySQL測試容器
首先讓我們看看如何啟動測試容器的基本操作。
需要導(dǎo)入testcontainers庫的以下兩個包:
import (
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/modules/mysql"
)
使用mysql.Run()并傳入以下參數(shù)來啟動MySQL容器:
- MySQL鏡像版本
- 數(shù)據(jù)庫名稱
- 用戶名和密碼
- 初始數(shù)據(jù)庫腳本
ctx := context.Background()
mysqlContainer, err := mysql.Run(ctx,
"mysql:8.0.36",
mysql.WithDatabase("example"),
mysql.WithUsername("appuser"),
mysql.WithPassword("passme"),
mysql.WithScripts(filepath.Join("testdata", "schema.sql")),
)
if err != nil {
log.Panicf("failed to start container: %s\n", err)
}
連接MySQL容器
要連接MySQL數(shù)據(jù)庫,需要導(dǎo)入Go的標(biāo)準(zhǔn)庫database/sql和MySQL驅(qū)動。為避免與testcontainer庫的MySQL模塊名稱沖突,給驅(qū)動包添加下劃線別名:
import (
_ "github.com/go-sql-driver/mysql"
"database/sql"
)
與Java類似,Go中的數(shù)據(jù)庫連接也是通過連接字符串實現(xiàn)。testcontainers庫提供了一個便捷函數(shù)mysqlContainer.ConnectionString(),它可以生成格式正確的連接字符串。
測試套件實現(xiàn)
首先,定義一個包含客戶DAO、MySQL容器和數(shù)據(jù)庫連接引用的測試套件結(jié)構(gòu)體。suite.Suite提供了測試框架的元素,如用于管理測試狀態(tài)和支持測試日志的testing.T。
type CustomerDaoTestSuite struct {
suite.Suite
dao *dao.CustomerDao
mysqlContainer *mysql.MySQLContainer
db *sql.DB
}
生命周期函數(shù)
Testify框架提供了以下接口來實現(xiàn)測試生命周期:
- SetupSuite() - 在所有測試場景執(zhí)行前調(diào)用一次(相當(dāng)于JUnit中的@BeforeAll)
- SetupTest() - 每個測試場景前的設(shè)置函數(shù)(相當(dāng)于JUnit中的@BeforeEach)
- TearDownTest() - 每個測試場景完成后的清理函數(shù)(相當(dāng)于JUnit中的@AfterEach)
- TearDownSuite() - 在所有測試場景結(jié)束后調(diào)用一次(相當(dāng)于JUnit中的@AfterAll)
最終思考
數(shù)據(jù)訪問層的集成測試至關(guān)重要。即使SQL語句在數(shù)據(jù)庫客戶端中運(yùn)行完美,放入程序代碼中也不能保證正常工作。使用真實數(shù)據(jù)庫進(jìn)行測試是確認(rèn)組件正常運(yùn)行的唯一方法。
借助testcontainers庫和Testify框架,集成測試可以自動啟動容器中的數(shù)據(jù)庫,并在完整的測試生命周期中將DAO連接到數(shù)據(jù)庫進(jìn)行測試,就像Java中的JUnit一樣。該庫對開發(fā)人員友好,只需幾個函數(shù)調(diào)用就能輕松完成測試設(shè)置。