精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Go項目實戰--數據Dao層代碼的單元測試實戰

數據庫 MongoDB
采用sqlmock類的工具,對Dao要執行的SQL作出預期匹配,同時Mock SQL查詢要返回的數據,保證Dao方法內部的邏輯正常執行。

Dao的單元測試

講到數據庫的單元測試,一般有那么幾個流派

  • 專門準備一個獨立的數據庫,單元測試時讓所有測試用例讀寫這個獨立的數據庫,它的優點是單測真的去讀寫數據庫啦,缺點嘛也顯而易見,一個項目的數據庫不是光有表就行,還得準備測試數據,這個搞起來就有點麻煩,尤其是關聯性強的數據,造起來更麻煩。
  • 讓項目在單元測試時訪問內存數據庫,它的優缺點其實跟上個差不多。
  • 采用sqlmock類的工具,對Dao要執行的SQL作出預期匹配,同時Mock SQL查詢要返回的數據,保證Dao方法內部的邏輯正常執行。

我們這里采用的是第三個流派,用 sqlmock 方式來做數據庫Dao的單元測試,本節的內容大綱主要如下:

圖片圖片

這里我們會用到DataDog家開發的go-sqlmock這個工具,先來安裝一下它:

github.com/DATA-DOG/go-sqlmock

安裝過程如下:

圖片圖片

單元測試入口TestMain的設置

我們計劃在 UserDao 和 OrderDao 中找幾個典型的方法來做單元測試的實戰,這里我們先在新建test/dao/user_test.go,創建完之后還不能馬上開始寫測試用例,我們再來做一下dao層單元測試的基礎工作。

在TestMain方法中初始化go-sqlmock ,這樣整個dao下的測試用例就都能使用它了,TestMain是在當前package下最先運行的一個函數,無論你運行哪個測試用例TestMain都會先被Go調用,所以它常用于測試基礎組件的初始化。

我們的TestMain的代碼如下:

var (
 mock sqlmock.Sqlmock
 err  error
 db   *sql.DB
)

func TestMain(m *testing.M) {
 db, mock, err = sqlmock.New()
if err != nil {
panic(err)
 }
// 把項目使用的DB連接換成sqlmock的DB連接
 dbMasterConn, _ := gorm.Open(mysql.New(mysql.Config{
  Conn:                      db,
  SkipInitializeWithVersion: true,
  DefaultStringSize:         0,
 }))
 dbSlaveConn, _ := gorm.Open(mysql.New(mysql.Config{
  Conn:                      db,
  SkipInitializeWithVersion: true,
  DefaultStringSize:         0,
 }))
 dao2.SetDBMasterConn(dbMasterConn)
 dao2.SetDBSlaveConn(dbSlaveConn)
 os.Exit(m.Run())
}

這里我們創建一個 go-sqlmock 的數據庫連接 和 mock對象,mock對象管理 db 預期要執行的SQL,具體初始化中各個參數的作用,直接看我上面代碼里的注視吧。

因我我們項目里Dao使用的數據庫連接在包外不可訪問,所以我在這里給項目dao層里加了 SetDBMasterConn,SetDBSlaveConn兩個方法把我們原本的數據庫連接替換成了sqlmock的數據庫連接。

基礎設置完成后,接下來我們分別找Dao的Insert、Update、Select操作來展示怎么給他們做單元測試。

Insert 操作的單元測試

首先給UserDao的CreateUser方法做單元測試,它是用戶注冊接口的邏輯中會用到的Dao方法,其定義如下:

func (ud *UserDao) CreateUser(userInfo *do.UserBaseInfo, userPasswordHash string) (*model.User, error) {
 userModel := new(model.User)
 err := util.CopyProperties(userModel, userInfo)
if err != nil {
  err = errcode.Wrap("UserDaoCreateUserError", err)
returnnil, err
 }
 userModel.Password = userPasswordHash

 err = DBMaster().WithContext(ud.ctx).Create(userModel).Error
if err != nil {
  err = errcode.Wrap("UserDaoCreateUserError", err)
returnnil, err
 }
return userModel, nil
}

這里就不再對CreateUser這個方法里都是什么做展開了,大家直接看項目代碼吧,它的單元測試如下:

func TestUserDao_CreateUser(t *testing.T) {
    userInfo := &do.UserBaseInfo{
        Nickname:  "Slang",
        LoginName: "slang@go-mall.com",
        Verified:  0,
        Avatar:    "",
        Slogan:    "happy!",
        IsBlocked: 0,
        CreatedAt: time.Now(),
        UpdatedAt: time.Now(),
    }
    passwordHash, _ := util.BcryptPassword("123456")
    userIsDel := 0

    ud := dao2.NewUserDao(context.TODO())
    mock.ExpectBegin()
    mock.ExpectExec(regexp.QuoteMeta("INSERT INTO `users`")).
    WithArgs(userInfo.Nickname, userInfo.LoginName, passwordHash, userInfo.Verified, userInfo.Avatar,
               userInfo.Slogan, userIsDel, userInfo.IsBlocked, userInfo.CreatedAt, userInfo.UpdatedAt).
    WillReturnResult(sqlmock.NewResult(1, 1))
    mock.ExpectCommit()
    userObj, err := ud.CreateUser(userInfo, passwordHash)
    assert.Nil(t, err)
    assert.Equal(t, userInfo.LoginName, userObj.LoginName)
}

這里我們首先自己初始化了一個CreateUser會用到的數據userInfo和passwordHash,然后使用 ExpectExec 指定預期要執行的SQL以及預期返回的結果。

這里我來說明一下sqlmock 默認使用 sqlmock.QueryMatcherRegex 作為默認的SQL匹配器,該匹配器使用mock.ExpectQuery 和 mock.ExpectExec 的參數作為正則表達式與真正執行的SQL語句進行匹配,如果使用QueryMatcherEqual 作為匹配器的話,那么我們寫預期SQL時就要寫完整的SQL了。

我推薦用默認的匹配器就行,因為接下來的WithArgs中我們還要給SQL的 ? 占位符提供參數值,這個參數值如果數量或者類型匹配不上的話,單測依然是無法通過的。

WillReturnResult(sqlmock.NewResult(1, 1)) 這行的意思是SQL執行后返回的 lastInsertId 是 1, 受影響行數也是 1。

拿到結果之后我們再做assert斷言,判斷結果是否符合預期。符合預期則通過,不符合的話測試用例會失敗。大家可以自己嘗試修改一下這個用例看它執行失敗的效果。

Select 查詢的單元測試

關于SQL查詢的單元測試,和上面的區別是我們會Mock返回的結果集,這里我們拿的是OrderDao的GetUserOrders做的單元測試,代碼如下。

func TestOrderDao_GetUserOrders(t *testing.T) {
    orderDel := soft_delete.DeletedAt(0)
    now := time.Now()
    emptyPayTime := time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC)

    orders := []*model.Order{
        {1, "12345675555", "", 1, 1, 100, 100, 0, 0, emptyPayTime, orderDel, now, now},
        {2, "12345675556", "", 1, 1, 100, 100, 0, 0, emptyPayTime, orderDel, now, now},
    }
    od := dao2.NewOrderDao(context.TODO())
    var userId int64 = 1
    offset := 10
    limit := 50
    mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `orders`")).WithArgs(userId, orderDel, limit, offset).
    WillReturnRows(
        sqlmock.NewRows([]string{"id", "order_no", "pay_trans_id", "pay_type", "user_id", "bill_money", "pay_money",
                                  "pay_state", "order_status", "paid_at", "is_del", "created_at", "updated_at"}).
        AddRow(
            orders[0].ID, orders[0].OrderNo, orders[0].PayTransId, orders[0].PayType, orders[0].UserId, orders[0].BillMoney, orders[0].PayMoney,
            orders[0].PayState, orders[0].OrderStatus, orders[0].PaidAt, orders[0].IsDel, orders[0].CreatedAt, orders[0].UpdatedAt,
        ).AddRow(
            orders[1].ID, orders[1].OrderNo, orders[1].PayTransId, orders[1].PayType, orders[1].UserId, orders[1].BillMoney, orders[1].PayMoney,
            orders[1].PayState, orders[1].OrderStatus, orders[1].PaidAt, orders[1].IsDel, orders[1].CreatedAt, orders[1].UpdatedAt,
        ),
    )
    mock.ExpectQuery(regexp.QuoteMeta("SELECT count(*) FROM `orders`")).WithArgs(userId, orderDel).
    WillReturnRows(sqlmock.NewRows([]string{"COUNT(*)"}).AddRow(2))
    gotOrders, totalRow, err := od.GetUserOrders(userId, offset, limit)
    assert.Nil(t, err)
    assert.Equal(t, orders, gotOrders)
    assert.Equal(t, totalRow, int64(2))
}

這里我用 ExpectQuery 指定了兩個預期要執行的SQL是為什么呢?因為GetUserOrders方法即返回了用戶訂單列表還返回了數據分頁用的totalRaws變量,大家可以試試把它刪掉看看這個單元測試能不能執行成功,這里我可以告訴你結果會成功但又沒完全成功,會有一條Warning警告,報告出有一個執行的SQL沒有做預期匹配。

執行單元測試時可以用上面我教的命令,也可以用IDE自帶的測試按鈕跑來跑這個測試用例。

Update操作的單元測試

Update操作的單元測試于Insert操作的類似,我們選用OrderDao的UpdateOrderStatus 方法來做單元測試。

func TestOrderDao_UpdateOrderStatus(t *testing.T) {
 orderNewStatus := 1
 var orderId int64 = 1
 orderDel := 0
 mock.ExpectBegin()
 mock.ExpectExec(regexp.QuoteMeta("UPDATE `orders` SET")).
  WithArgs(orderNewStatus, AnyTime{}, orderId, orderDel).
  WillReturnResult(sqlmock.NewResult(1, 1))
 mock.ExpectCommit()
 od := dao2.NewOrderDao(context.TODO())
 err := od.UpdateOrderStatus(orderId, orderNewStatus)
 assert.Nil(t, err)
}

這里的AnyTime是咱們自定義的一個類型

type AnyTime struct{}

func (a AnyTime) Match(v driver.Value) bool {
 // Match 方法中:判斷字段值只要是time.Time 類型,就能驗證通過
 _, ok := v.(time.Time)
 return ok
}

其實在使用SQL完全匹配模式時才必須用它,因為參數提供的Time.Now()做為UpdatedAt的時間,這與SQL執行時真正的UpdateAt時間是有很小的差異的,這個時候我們可以提供AnyTime做為更新時間,這樣sqlmock在做預期SQL和實際SQL的匹配時,遇到了AnyTime類型的預期值,就會按照這里指定的規則,判斷字段值只要是time.Time 類型就能驗證通過。

總結

本節代碼版本為c19.1

git fetch --tags
git checkout tags/c19.1

訪問 https://github.com/go-study-lab/go-mall/compare/c18...c19.1 可在線查看詳細的代碼更新。

責任編輯:武曉燕 來源: 網管叨bi叨
相關推薦

2025-05-07 09:06:03

2025-05-15 09:05:19

Go項目BDD測試

2022-04-08 09:01:56

腳本Go應用單元

2021-04-23 07:33:10

SpringSecurity單元

2017-01-14 23:42:49

單元測試框架軟件測試

2022-08-02 08:07:24

單元測試代碼重構

2024-01-09 08:08:12

Go單元測試系統

2023-07-27 08:16:51

數據訪問層項目

2023-10-28 10:10:41

2025-08-28 01:00:00

Go單元測試

2023-07-26 08:58:45

Golang單元測試

2022-10-26 08:00:49

單元測試React

2011-05-16 16:52:09

單元測試徹底測試

2017-03-30 07:56:30

測試前端代碼

2011-07-27 17:02:12

Xcode iPhone 單元測試

2009-06-26 17:48:38

JSF項目單元測試JSFUnit

2021-09-18 15:40:03

Vue單元測試命令

2023-07-28 10:27:48

Java單元測試

2017-01-16 12:12:29

單元測試JUnit

2017-01-14 23:26:17

單元測試JUnit測試
點贊
收藏

51CTO技術棧公眾號

欧美日韩国产中文精品字幕自在自线| 国精产品一区一区三区mba视频| 日韩h在线观看| 久久人妻精品白浆国产| 黄色一级片在线观看| av电影天堂一区二区在线| 国产精品电影一区| 国产 日韩 欧美 成人| 精品一区毛片| 精品日韩一区二区三区| 奇米影音第四色| 不卡专区在线| 亚洲精选视频免费看| 你懂的网址一区二区三区| 91片黄在线观看喷潮| 亚洲一区亚洲| 欧美片一区二区三区| 欧洲美熟女乱又伦| 日韩欧美在线精品| 日韩一级黄色大片| 色播五月综合网| 亚洲一级少妇| 亚洲一区二区三区视频在线播放| 亚洲精品无人区| 欧美女同网站| 99精品视频在线观看| 91久久精品国产91久久性色tv| 亚洲精品一区二三区| 夜夜精品视频| 欧美激情亚洲一区| h色网站在线观看| 欧美日韩一区二区三区视频播放| 亚洲国产精品系列| 久久aaaa片一区二区| 国外成人福利视频| 色88888久久久久久影院野外| 黄色一级片在线看| 日韩欧美一起| 一区二区国产盗摄色噜噜| 中文字幕在线亚洲三区| www.亚洲.com| 国产精品区一区二区三区| 欧美1o一11sex性hdhd| 色欲久久久天天天综合网| 国产二区国产一区在线观看| 91精品啪在线观看麻豆免费| 在线观看xxxx| 久久成人精品无人区| 国产精品亚洲综合天堂夜夜| 中国黄色一级视频| 男人的天堂亚洲一区| 国产精品久久久久免费a∨大胸 | 欧美日韩国产精品专区| 国产日韩欧美精品在线观看| av免费不卡| 五月天久久比比资源色| 国产成人无码a区在线观看视频| jizz一区二区三区| 五月激情综合色| 欧美 日韩精品| 亚洲精品在线影院| 欧美日韩亚洲综合在线| 一区二区三区四区毛片| 免费一级欧美在线大片| 欧美zozo另类异族| 久久久久久久无码| 久久成人高清| yellow中文字幕久久| 国产极品美女在线| 欧美日韩精品免费观看视频完整| 久久久久久久久久久国产| 天天操天天摸天天干| 日韩精品乱码av一区二区| 国产精品永久免费视频| 精品人妻久久久久一区二区三区 | 黄页网站在线观看免费| 午夜精品福利一区二区三区av| 日本精品一区在线观看| 国产精品久久久久久吹潮| 欧美精品1区2区3区| 亚洲欧美综合视频| 国产区精品区| 久久天天躁日日躁| 久久精品视频8| 久久精品女人天堂| 91日韩在线播放| 日本激情一区二区三区| 国产清纯白嫩初高生在线观看91| 一区二区三区四区欧美日韩| 欧美aaaxxxx做受视频| 欧美性猛交xxxx乱大交3| 91国内在线播放| 欧美a大片欧美片| 丝袜亚洲欧美日韩综合| 国产极品在线播放| 久99久精品视频免费观看| 国产精品久久久久久免费观看 | 99久久婷婷国产综合精品电影√| 久久久久久网址| 黄色一区二区视频| 不卡一二三区首页| 亚洲视频导航| 特黄毛片在线观看| 日韩一区国产二区欧美三区| 四虎影成人精品a片| 你懂的亚洲视频| 国产精品成人v| 凸凹人妻人人澡人人添| 亚洲人妖av一区二区| 日韩有码免费视频| 红杏一区二区三区| 久久精品影视伊人网| 蜜臀精品一区二区三区| 成人涩涩免费视频| 偷拍盗摄高潮叫床对白清晰| 唐人社导航福利精品| 欧美精品一区二区高清在线观看| 亚洲欧美日韩第一页| 久久精品午夜| 国产亚洲欧美一区二区| 在线āv视频| 欧美日韩一级视频| 中文字幕第4页| 在线一区免费观看| 国产精华一区二区三区| caoporm免费视频在线| 欧美日韩一区在线| 亚洲女优在线观看| 久久性天堂网| 免费久久久一本精品久久区| 高清精品在线| 亚洲国产精品va| 国产真实夫妇交换视频| 国产黄色91视频| 日本一区二区三区四区五区六区| 欧美亚洲福利| 在线亚洲男人天堂| 中文字幕日本人妻久久久免费| 久久久久综合网| aaaaaa亚洲| 自拍亚洲一区| 国产成人午夜视频网址| 国产毛片在线| 在线视频综合导航| 亚洲综合欧美综合| 美女性感视频久久| 亚洲综合欧美日韩| 91国产一区| 欧美不卡视频一区发布| 精品人妻少妇AV无码专区| 亚洲精品成人a在线观看| 两女双腿交缠激烈磨豆腐| 97人人精品| 亚洲自拍小视频| 欧美人与动牲性行为| 精品国产髙清在线看国产毛片| 久久久久成人片免费观看蜜芽 | 我想看黄色大片| 麻豆一区二区99久久久久| 综合网五月天| 天堂av一区| 国内揄拍国内精品| 免费在线黄色影片| 欧美日韩成人在线一区| 国产又黄又爽又无遮挡| 成人av在线影院| 国产成人久久777777| 成人a'v在线播放| 国产日韩欧美中文| 色婷婷视频在线观看| 亚洲精品国产美女| 精品乱码一区内射人妻无码| 亚洲欧洲在线观看av| jjzz黄色片| 日精品一区二区三区| 黄瓜视频免费观看在线观看www | 外国电影一区二区| 不卡av在线网站| 无码国精品一区二区免费蜜桃| 色伊人久久综合中文字幕| 99久久久无码国产精品不卡| 国产成人在线影院| 黄色片一级视频| 91久久电影| 久久精品国产一区二区三区日韩| 久久电影天堂| 97久久超碰福利国产精品…| 91欧美在线视频| 亚洲成人av在线| 中文字幕日本人妻久久久免费 | 国产一级片自拍| 亚洲经典在线| 一区二区三区电影| 日韩电影不卡一区| 91手机视频在线观看| 小视频免费在线观看| 久久久av一区| 美国一级片在线免费观看视频| 欧美一区二区二区| 黄色av一区二区| 亚洲3atv精品一区二区三区| 亚洲欧洲综合网| 2022国产精品视频| 四川一级毛毛片| 精品一区二区免费看| 国产乱子夫妻xx黑人xyx真爽| 91精品一区二区三区综合| 久久综合一区| 国产精品欧美大片| 91在线观看免费高清完整版在线观看| 热色播在线视频| 久久久久久久久久久免费| 69久久精品| 亚洲色图综合网| 人人妻人人澡人人爽人人欧美一区| 欧美日韩高清在线| 91视频在线视频| 偷拍日韩校园综合在线| 欧美日韩综合一区二区| 国产精品久久久久久久久久免费看| www.超碰97| www.色精品| 北京富婆泄欲对白| 国产成人免费在线观看| 国产精品久久久久久久99| 日韩高清在线电影| av免费在线播放网站| 久久国产直播| 日本三级免费观看| 国产模特精品视频久久久久| 久久99久久99精品| 国产精品啊啊啊| 欧洲金发美女大战黑人| 亚洲精品二区三区| av不卡在线免费观看| 久久网站免费观看| 亚洲精品一品区二品区三品区| 精品国产一区二区三区av片| 日本视频一区在线观看| 久久av网址| 台湾成人av| 不卡一区综合视频| 日本亚洲欧洲精品| 欧美色蜜桃97| 欧美主播一区二区三区美女 久久精品人| 日韩激情网站| 日本一区二区三区视频在线播放| 免费一区二区| 天堂资源在线亚洲资源| 久久人人99| 欧美极品少妇无套实战| 99riav1国产精品视频| 国产女大学生av| 水野朝阳av一区二区三区| 苍井空浴缸大战猛男120分钟| 日韩黄色免费网站| 不卡的av中文字幕| 国产精品一区二区果冻传媒| 亚洲av永久无码精品| 91视频在线观看免费| 国产又黄又粗视频| 日韩理论片中文av| 久一区二区三区| 一本到不卡精品视频在线观看| 国产精品第6页| 欧美一区二区三区喷汁尤物| 天天操天天舔天天干| 亚洲色在线视频| bt在线麻豆视频| 亚州国产精品久久久| 欧美123区| 91九色极品视频| 亚洲制服一区| 国产福利片一区二区| 亚洲黄页一区| 污污动漫在线观看| 粉嫩aⅴ一区二区三区四区五区| aa片在线观看视频在线播放| 中文成人av在线| 久久精品国产av一区二区三区| 日韩欧美综合在线视频| 国产孕妇孕交大片孕| 亚洲精品美女在线观看| 在线免费看av| 97色在线视频| av一级久久| 欧美日韩系列| 亚洲午夜激情在线| 国产精品亚洲a| 国产一区欧美日韩| 鲁大师私人影院在线观看| 国产精品免费免费| 日韩精品国产一区二区| 欧美亚洲高清一区二区三区不卡| www.麻豆av| 这里只有视频精品| 97人人爽人人澡人人精品| 国产日韩精品入口| 日韩av系列| 国产a级黄色大片| 日本成人中文字幕| 久久久久9999| 亚洲精品视频免费观看| 成人黄色三级视频| 日韩成人高清在线| 性欧美ⅴideo另类hd| 国产精品精品一区二区三区午夜版| 6080成人| 91免费网站视频| 日本中文字幕一区二区视频 | 久热精品在线观看视频| 91小视频免费看| 久久影院一区二区| 欧美一区二区三区成人| 亚洲视频tv| 国产成人福利视频| 欧洲亚洲一区二区三区| 日韩 欧美 视频| 国产美女精品人人做人人爽| 男人天堂资源网| 欧美在线观看视频一区二区三区| 污污网站免费在线观看| 欧美激情综合色综合啪啪五月| 成人在线视频免费| 亚洲精品一卡二卡三卡四卡| 三级在线观看一区二区| 亚洲久久久久久久| 色综合咪咪久久| 欧洲免费在线视频| 欧美在线日韩在线| 亚洲精品aaaaa| 国产精品99久久免费黑人人妻| 91亚洲精品一区二区乱码| 日韩精品一区二区三区国语自制 | 国产1区2区3区在线| 2019最新中文字幕| 青青草原在线亚洲| 日本www在线播放| 久久综合色8888| 久久久免费高清视频| 亚洲精品自拍偷拍| 92国产精品| 日韩成人av网站| 蜜桃久久久久久久| 看黄色录像一级片| 欧美精品第1页| 啪啪免费视频一区| 国产欧美日韩一区| 亚洲永久字幕| 性猛交ⅹxxx富婆video| 欧美日本国产视频| 中文字幕在线三区| 国产高清精品一区| 国产一区二区你懂的| 男人天堂av电影| 欧美亚洲综合另类| 黄色av电影在线播放| 国产成人成网站在线播放青青| 亚洲天堂偷拍| 中文字幕一区二区三区人妻不卡| 在线免费观看视频一区| 日本在线观看www| 成人看片视频| 性欧美xxxx大乳国产app| 亚洲精品成人av久久| 91精品在线免费观看| 久久免费电影| 欧美精品欧美精品系列c| 麻豆成人免费电影| 青娱乐国产精品| 国产视频精品va久久久久久| 草民电影神马电影一区二区| 国产一二三四五| 91麻豆国产香蕉久久精品| 中国一级特黄视频| 欧美日韩国产va另类| 欧美日韩123| 欧美性猛交xxxx乱大交91| 欧美日韩一二三四五区| 色三级在线观看| 韩国成人av| 久久超碰97中文字幕| 国产成人亚洲精品自产在线| 中文字幕在线看视频国产欧美在线看完整| 91成人短视频在线观看| 茄子视频成人免费观看| 亚洲久草在线视频| 国产一区电影| 国产精品传媒毛片三区| 蜜臀av性久久久久蜜臀av麻豆| 欧美丰满艳妇bbwbbw| 亚洲深夜福利视频| 91精品尤物| 日本三级黄色网址| 精品国产电影一区| 黄色成年人视频在线观看| 欧美日韩一区二区三区在线观看免 | 丰满人妻一区二区三区无码av| 国产精品 欧美在线| 尤物网精品视频|