欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Go單元測(cè)試對(duì)數(shù)據(jù)庫(kù)CRUD進(jìn)行Mock測(cè)試

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

前言

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

這是Go語(yǔ)言單元測(cè)試系列教程的第3篇,介紹了如何使用go-sqlmockminiredis工具進(jìn)行MySQLRedismock測(cè)試。

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

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

go-sqlmock

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

安裝

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

使用示例

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

  • 在表中將當(dāng)前商品的瀏覽次數(shù)+1
  • product_viewers表中記錄瀏覽當(dāng)前商品的用戶id
//?app.go
package?main
import?"database/sql"
//?recordStats?記錄用戶瀏覽產(chǎn)品信息
func?recordStats(db?*sql.DB,?userID,?productID?int64)?(err?error)?{
?//?開(kāi)啟事務(wù)
?//?操作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()?{
?//?注意:測(cè)試的過(guò)程中并不需要真正的連接
?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ù)編寫單元測(cè)試,但是又不想在測(cè)試過(guò)程中連接真實(shí)的數(shù)據(jù)庫(kù)進(jìn)行測(cè)試。這個(gè)時(shí)候我們就可以像下面示例代碼中那樣使用sqlmock工具去mock數(shù)據(jù)庫(kù)操作。

package?main
import?(
?"fmt"
?"testing"
?"github.com/DATA-DOG/go-sqlmock"
)
//?TestShouldUpdateStats?sql執(zhí)行成功的測(cè)試用例
func?TestShouldUpdateStats(t?*testing.T)?{
?//?mock一個(gè)*sql.DB對(duì)象,不需要連接真實(shí)的數(shù)據(jù)庫(kù)
?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語(yǔ)句時(shí)的返回結(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對(duì)象傳入我們的函數(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í)行失敗回滾的測(cè)試用例
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)
?}
}

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

執(zhí)行單元測(cè)試,看一下最終的測(cè)試結(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

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

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

miniredis

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

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

當(dāng)我們?yōu)橐恍┌琑edis操作的代碼編寫單元測(cè)試時(shí)就可以使用它來(lái)mock Redis操作。

安裝

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

使用示例

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

//?redis_op.go
package?miniredis_demo
import?(
?"context"
?"github.com/go-redis/redis/v8"?//?注意導(dǎo)入版本
?"strings"
?"time"
)
const?(
?KeyValidWebsite?=?"app:valid:website:list"
)
func?DoSomethingWithRedis(rdb?*redis.Client,?key?string)?bool?{
?//?這里可以是對(duì)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?五秒過(guò)期
?if?err?:=?rdb.Set(ctx,?"blog",?val,?5*time.Second).Err();?err?!=?nil?{
??return?false
?}
?return?true
}

下面的代碼是我使用miniredis庫(kù)為DoSomethingWithRedis函數(shù)編寫的單元測(cè)試代碼,其中miniredis不僅支持mock常用的Redis操作,還提供了很多實(shí)用的幫助函數(shù),例如檢查key的值是否與預(yù)期相等的s.CheckGet()和幫助檢查key過(guò)期時(shí)間的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一個(gè)redis?server
?s,?err?:=?miniredis.Run()
?if?err?!=?nil?{
??panic(err)
?}
?defer?s.Close()
?//?準(zhǔn)備數(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()
?}
?//?可以手動(dòng)檢查redis中的值是否復(fù)合預(yù)期
?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")
?//?過(guò)期檢查
?s.FastForward(5?*?time.Second)?//?快進(jìn)5秒
?if?s.Exists("blog")?{
??t.Fatal("'blog'?should?not?have?existed?anymore")
?}
}

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

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

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

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

總結(jié)

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

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

相關(guān)文章

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

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

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

    Go語(yǔ)言學(xué)習(xí)技巧之如何合理使用Pool

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

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

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

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

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

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

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

    Go+Lua解決Redis秒殺中庫(kù)存與超賣問(wèn)題

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

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

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

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

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

    Go語(yǔ)言格式化動(dòng)詞使用詳解

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

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

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

最新評(píng)論