亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go單元測試對數(shù)據(jù)庫CRUD進行Mock測試

 更新時間:2022年06月21日 16:07:07   作者:李文周  
這篇文章主要為大家介紹了Go單元測試對數(shù)據(jù)庫CRUD進行Mock測試的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

前言

最近在實踐中也總結(jié)了一些如何用表格驅(qū)動的方式使用 gock Mock測試外部接口調(diào)用。以及怎么對GORM做mock測試,這些等這篇學完基礎(chǔ)后,后面再單獨寫文章給大家介紹。

這是Go語言單元測試系列教程的第3篇,介紹了如何使用go-sqlmockminiredis工具進行MySQLRedismock測試。

在上一篇《Go單元測試--模擬服務請求和接口返回》中,我們介紹了如何使用httptest和gock工具進行網(wǎng)絡測試。

除了網(wǎng)絡依賴之外,我們在開發(fā)中也會經(jīng)常用到各種數(shù)據(jù)庫,比如常見的MySQL和Redis等。本文就分別舉例來演示如何在編寫單元測試的時候?qū)ySQL和Redis進行mock。

go-sqlmock

sqlmock 是一個實現(xiàn) sql/driver 的mock庫。它不需要建立真正的數(shù)據(jù)庫連接就可以在測試中模擬任何 sql 驅(qū)動程序的行為。使用它可以很方便的在編寫單元測試的時候mock sql語句的執(zhí)行結(jié)果。

安裝

go?get?github.com/DATA-DOG/go-sqlmock

使用示例

這里使用的是go-sqlmock官方文檔中提供的基礎(chǔ)示例代碼。在下面的代碼中,我們實現(xiàn)了一個recordStats函數(shù)用來記錄用戶瀏覽商品時產(chǎn)生的相關(guān)數(shù)據(jù)。具體實現(xiàn)的功能是在一個事務中進行以下兩次SQL操作:

  • 在表中將當前商品的瀏覽次數(shù)+1
  • product_viewers表中記錄瀏覽當前商品的用戶id
//?app.go
package?main
import?"database/sql"
//?recordStats?記錄用戶瀏覽產(chǎn)品信息
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表中插入一條數(shù)據(jù)
?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的產(chǎn)品
?if?err?=?recordStats(db,?1?/*some?user?id*/,?5?/*some?product?id*/);?err?!=?nil?{
??panic(err)
?}
}

現(xiàn)在我們需要為代碼中的recordStats函數(shù)編寫單元測試,但是又不想在測試過程中連接真實的數(shù)據(jù)庫進行測試。這個時候我們就可以像下面示例代碼中那樣使用sqlmock工具去mock數(shù)據(jù)庫操作。

package?main
import?(
?"fmt"
?"testing"
?"github.com/DATA-DOG/go-sqlmock"
)
//?TestShouldUpdateStats?sql執(zhí)行成功的測試用例
func?TestShouldUpdateStats(t?*testing.T)?{
?//?mock一個*sql.DB對象,不需要連接真實的數(shù)據(jù)庫
?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執(zhí)行指定SQL語句時的返回結(jié)果
?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對象傳入我們的函數(shù)中
?if?err?=?recordStats(db,?2,?3);?err?!=?nil?{
??t.Errorf("error?was?not?expected?while?updating?stats:?%s",?err)
?}
?//?確保期望的結(jié)果都滿足
?if?err?:=?mock.ExpectationsWereMet();?err?!=?nil?{
??t.Errorf("there?were?unfulfilled?expectations:?%s",?err)
?}
}
//?TestShouldRollbackStatUpdatesOnFailure?sql執(zhí)行失敗回滾的測試用例
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)
?}
}

上面的代碼中,定義了一個執(zhí)行成功的測試用例和一個執(zhí)行失敗回滾的測試用例,確保我們代碼中的每個邏輯分支都能被測試到,提高單元測試覆蓋率的同時也保證了代碼的健壯性。

執(zhí)行單元測試,看一下最終的測試結(jié)果。

? 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

可以看到兩個測試用例的結(jié)果都符合預期,單元測試通過。

在很多使用ORM工具的場景下,也可以使用go-sqlmock庫mock數(shù)據(jù)庫操作進行測試。

miniredis

除了經(jīng)常用到MySQL外,Redis在日常開發(fā)中也會經(jīng)常用到。接下來的這一小節(jié),我們將一起學習如何在單元測試中mock Redis的相關(guān)操作。

miniredis是一個純go實現(xiàn)的用于單元測試的redis server。它是一個簡單易用的、基于內(nèi)存的redis替代品,它具有真正的TCP接口,你可以把它當成是redis版本的net/http/httptest。

當我們?yōu)橐恍┌琑edis操作的代碼編寫單元測試時就可以使用它來mock Redis操作。

安裝

go?get?github.com/alicebob/miniredis/v2

使用示例

這里以github.com/go-redis/redis庫為例,編寫了一個包含若干Redis操作的DoSomethingWithRedis函數(shù)。

//?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
?}
?//?設(shè)置?blog?key?五秒過期
?if?err?:=?rdb.Set(ctx,?"blog",?val,?5*time.Second).Err();?err?!=?nil?{
??return?false
?}
?return?true
}

下面的代碼是我使用miniredis庫為DoSomethingWithRedis函數(shù)編寫的單元測試代碼,其中miniredis不僅支持mock常用的Redis操作,還提供了很多實用的幫助函數(shù),例如檢查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()
?//?準備數(shù)據(jù)
?s.Set("q1mi",?"liwenzhou.com")
?s.SAdd(KeyValidWebsite,?"q1mi")
?//?連接mock的redis?server
?rdb?:=?redis.NewClient(&redis.Options{
??Addr:?s.Addr(),?//?mock?redis?server的地址
?})
?//?調(diào)用函數(shù)
?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")
?}
}

執(zhí)行執(zhí)行測試,查看單元測試結(jié)果:

? go test -v
=== RUN   ;TestDoSomethingWithRedis
--- PASS: TestDoSomethingWithRedis (0.00s)
PASS
ok      golang-unit-test-demo/miniredis_demo    0.052s

miniredis基本上支持絕大多數(shù)的Redis命令,大家可以通過查看文檔了解更多用法。

當然除了使用miniredis搭建本地redis server這種方法外,還可以使用各種打樁工具對具體方法進行打樁。在編寫單元測試時具體使用哪種mock方式還是要根據(jù)實際情況來決定。

總結(jié)

在日常工作開發(fā)中為代碼編寫單元測試時如何處理數(shù)據(jù)庫的依賴是最常見的問題,本文介紹了如何使用go-sqlmockminiredis工具mock相關(guān)依賴。

接下來,我們將更進一步,詳細介紹如何在編寫單元測試時mock接口實現(xiàn),更多關(guān)于Go數(shù)據(jù)庫CRUD Mock測試的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺談Golang是如何讀取文件內(nèi)容的(7種)

    淺談Golang是如何讀取文件內(nèi)容的(7種)

    這篇文章主要介紹了淺談Golang是如何讀取文件內(nèi)容的,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-05-05
  • Go語言學習技巧之如何合理使用Pool

    Go語言學習技巧之如何合理使用Pool

    這篇文章主要給大家介紹了關(guān)于Go語言學習技巧之如何合理使用Pool的相關(guān)資料,Pool用于存儲那些被分配了但是沒有被使用,而未來可能會使用的值,以減小垃圾回收的壓力。文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下。
    2017-12-12
  • 一文詳解Go語言中的Option設(shè)計模式

    一文詳解Go語言中的Option設(shè)計模式

    這篇文章主要為大家詳細介紹了Go語言中Option設(shè)計模式的相關(guān)知識,文中的示例代碼講解詳細,具有一定的學習價值,感興趣的可以了解一下
    2023-05-05
  • 如何使用大學教育郵箱下載golang等軟件(推薦)

    如何使用大學教育郵箱下載golang等軟件(推薦)

    這篇文章主要介紹了如何使用大學教育郵箱下載goland等軟件,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-09-09
  • Go 語言數(shù)組和切片的區(qū)別詳解

    Go 語言數(shù)組和切片的區(qū)別詳解

    本文主要介紹了Go 語言數(shù)組和切片的區(qū)別詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-04-04
  • Go+Lua解決Redis秒殺中庫存與超賣問題

    Go+Lua解決Redis秒殺中庫存與超賣問題

    本文主要介紹了Go+Lua解決Redis秒殺中庫存與超賣問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-03-03
  • Go Gin實現(xiàn)文件上傳下載的示例代碼

    Go Gin實現(xiàn)文件上傳下載的示例代碼

    這篇文章主要介紹了Go Gin實現(xiàn)文件上傳下載的示例代碼,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-04-04
  • golang內(nèi)置函數(shù)len的小技巧

    golang內(nèi)置函數(shù)len的小技巧

    len是很常用的內(nèi)置函數(shù),可以測量字符串、slice、array、channel以及map的長度/元素個數(shù)。本文就來介紹一下其他小技巧,感興趣的可以了解一下
    2021-07-07
  • Go語言格式化動詞使用詳解

    Go語言格式化動詞使用詳解

    這篇文章主要介紹了Go語言格式化動詞使用詳解的相關(guān)資料,需要的朋友可以參考下
    2023-08-08
  • Golang基于Vault實現(xiàn)敏感信息保護

    Golang基于Vault實現(xiàn)敏感信息保護

    Vault?是一個強大的敏感信息管理工具,自帶了多種認證引擎和密碼引擎,本文主要探討應用程序如何安全地從?Vault?獲取敏感信息,并進一步實現(xiàn)自動輪轉(zhuǎn),感興趣的可以了解一下
    2023-06-06

最新評論