您好,登錄后才能下訂單哦!
今天小編給大家分享一下Go怎么對數據庫CRUD進行Mock測試的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
sqlmock 是一個實現 sql/driver
的mock庫。它不需要建立真正的數據庫連接就可以在測試中模擬任何 sql 驅動程序的行為。使用它可以很方便的在編寫單元測試的時候mock sql語句的執行結果。
go get github.com/DATA-DOG/go-sqlmock
這里使用的是go-sqlmock
官方文檔中提供的基礎示例代碼。在下面的代碼中,我們實現了一個recordStats
函數用來記錄用戶瀏覽商品時產生的相關數據。具體實現的功能是在一個事務中進行以下兩次SQL操作:
在表中將當前商品的瀏覽次數+1
在product_viewers
表中記錄瀏覽當前商品的用戶id
// app.go package main import "database/sql" // recordStats 記錄用戶瀏覽產品信息 func recordStats(db *sql.DB, userID, productID int64) (err error) { // 開啟事務 // 操作views和product_viewers兩張表 tx, err := db.Begin() if err != nil { return } defer func() { switch err { case nil: err = tx.Commit() default: tx.Rollback() } }() // 更新products表 if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil { return } // product_viewers表中插入一條數據 if _, err = tx.Exec( "INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil { return } return } func main() { // 注意:測試的過程中并不需要真正的連接 db, err := sql.Open("mysql", "root@/blog") if err != nil { panic(err) } defer db.Close() // userID為1的用戶瀏覽了productID為5的產品 if err = recordStats(db, 1 /*some user id*/, 5 /*some product id*/); err != nil { panic(err) } }
現在我們需要為代碼中的recordStats
函數編寫單元測試,但是又不想在測試過程中連接真實的數據庫進行測試。這個時候我們就可以像下面示例代碼中那樣使用sqlmock
工具去mock數據庫操作。
package main import ( "fmt" "testing" "github.com/DATA-DOG/go-sqlmock" ) // TestShouldUpdateStats sql執行成功的測試用例 func TestShouldUpdateStats(t *testing.T) { // mock一個*sql.DB對象,不需要連接真實的數據庫 db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() // mock執行指定SQL語句時的返回結果 mock.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectCommit() // 將mock的DB對象傳入我們的函數中 if err = recordStats(db, 2, 3); err != nil { t.Errorf("error was not expected while updating stats: %s", err) } // 確保期望的結果都滿足 if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } } // TestShouldRollbackStatUpdatesOnFailure sql執行失敗回滾的測試用例 func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("an error '%s' was not expected when opening a stub database connection", err) } defer db.Close() mock.ExpectBegin() mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1)) mock.ExpectExec("INSERT INTO product_viewers"). WithArgs(2, 3). WillReturnError(fmt.Errorf("some error")) mock.ExpectRollback() // now we execute our method if err = recordStats(db, 2, 3); err == nil { t.Errorf("was expecting an error, but there was none") } // we make sure that all expectations were met if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("there were unfulfilled expectations: %s", err) } }
上面的代碼中,定義了一個執行成功的測試用例和一個執行失敗回滾的測試用例,確保我們代碼中的每個邏輯分支都能被測試到,提高單元測試覆蓋率的同時也保證了代碼的健壯性。
執行單元測試,看一下最終的測試結果。
? go test -v
=== RUN TestShouldUpdateStats
--- PASS: TestShouldUpdateStats (0.00s)
=== RUN TestShouldRollbackStatUpdatesOnFailure
--- PASS: TestShouldRollbackStatUpdatesOnFailure (0.00s)
PASS
ok golang-unit-test-demo/sqlmock_demo 0.011s
可以看到兩個測試用例的結果都符合預期,單元測試通過。
在很多使用ORM工具的場景下,也可以使用go-sqlmock
庫mock數據庫操作進行測試。
除了經常用到MySQL外,Redis在日常開發中也會經常用到。接下來的這一小節,我們將一起學習如何在單元測試中mock Redis的相關操作。
miniredis是一個純go實現的用于單元測試的redis server。它是一個簡單易用的、基于內存的redis替代品,它具有真正的TCP接口,你可以把它當成是redis版本的net/http/httptest
。
當我們為一些包含Redis操作的代碼編寫單元測試時就可以使用它來mock Redis操作。
go get github.com/alicebob/miniredis/v2
這里以github.com/go-redis/redis
庫為例,編寫了一個包含若干Redis操作的DoSomethingWithRedis
函數。
// redis_op.go package miniredis_demo import ( "context" "github.com/go-redis/redis/v8" // 注意導入版本 "strings" "time" ) const ( KeyValidWebsite = "app:valid:website:list" ) func DoSomethingWithRedis(rdb *redis.Client, key string) bool { // 這里可以是對redis操作的一些邏輯 ctx := context.TODO() if !rdb.SIsMember(ctx, KeyValidWebsite, key).Val() { return false } val, err := rdb.Get(ctx, key).Result() if err != nil { return false } if !strings.HasPrefix(val, "https://") { val = "https://" + val } // 設置 blog key 五秒過期 if err := rdb.Set(ctx, "blog", val, 5*time.Second).Err(); err != nil { return false } return true }
下面的代碼是我使用miniredis
庫為DoSomethingWithRedis
函數編寫的單元測試代碼,其中miniredis
不僅支持mock常用的Redis操作,還提供了很多實用的幫助函數,例如檢查key的值是否與預期相等的s.CheckGet()
和幫助檢查key過期時間的s.FastForward()
。
// redis_op_test.go package miniredis_demo import ( "github.com/alicebob/miniredis/v2" "github.com/go-redis/redis/v8" "testing" "time" ) func TestDoSomethingWithRedis(t *testing.T) { // mock一個redis server s, err := miniredis.Run() if err != nil { panic(err) } defer s.Close() // 準備數據 s.Set("q1mi", "liwenzhou.com") s.SAdd(KeyValidWebsite, "q1mi") // 連接mock的redis server rdb := redis.NewClient(&redis.Options{ Addr: s.Addr(), // mock redis server的地址 }) // 調用函數 ok := DoSomethingWithRedis(rdb, "q1mi") if !ok { t.Fatal() } // 可以手動檢查redis中的值是否復合預期 if got, err := s.Get("blog"); err != nil || got != "https://liwenzhou.com" { t.Fatalf("'blog' has the wrong value") } // 也可以使用幫助工具檢查 s.CheckGet(t, "blog", "https://liwenzhou.com") // 過期檢查 s.FastForward(5 * time.Second) // 快進5秒 if s.Exists("blog") { t.Fatal("'blog' should not have existed anymore") } }
執行執行測試,查看單元測試結果:
? go test -v
=== RUN TestDoSomethingWithRedis
--- PASS: TestDoSomethingWithRedis (0.00s)
PASS
ok golang-unit-test-demo/miniredis_demo 0.052s
miniredis
基本上支持絕大多數的Redis命令,大家可以通過查看文檔了解更多用法。
當然除了使用miniredis
搭建本地redis server這種方法外,還可以使用各種打樁工具對具體方法進行打樁。在編寫單元測試時具體使用哪種mock方式還是要根據實際情況來決定。
以上就是“Go怎么對數據庫CRUD進行Mock測試”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。