精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

国产日韩三区| 欧美色涩在线第一页| 国产精品免费在线| 麻豆精品久久久久久久99蜜桃| 精品久久不卡| 日韩一区国产二区欧美三区| 青青青免费在线| 91视频在线观看| 国产91丝袜在线观看| 日韩av男人的天堂| 久久国产精品二区| 日韩成人综合| 日韩精品高清在线| 91蝌蚪视频在线| 成人福利视频| 亚洲一区二区在线播放相泽 | 亚洲天堂2021av| 欧美日本不卡| 日韩视频免费在线| 中文字幕xxx| 成人av动漫| 555www色欧美视频| caoporn超碰97| 国产丝袜视频在线播放| 中文字幕一区二区三区蜜月| 久久久久免费网| 亚洲精品人妻无码| 久久精品72免费观看| 国产成人在线亚洲欧美| 日本在线小视频| 欧美福利一区| 久久五月天综合| 九九热久久免费视频| 亚洲婷婷丁香| 日韩精品视频在线播放| 99免费观看视频| 久久在线观看| 欧美一区二区三区在线看| 一区二区三区国产免费| 日韩电影大全网站| 黑人巨大精品欧美一区二区一视频| 中国女人做爰视频| 二区三区四区高清视频在线观看| 欧美激情在线观看视频免费| 久久资源亚洲| 欧美日韩免费做爰大片| 91蜜桃视频在线| 蜜桃视频日韩| 欧美老女人性开放| 久久久久久99久久久精品网站| 久久99精品国产99久久| 无码国产精品高潮久久99| 成人av电影在线观看| 国产精品一区二区三区观看| 午夜精品一区二区三| 国精品产品一区| 91黄色免费版| 亚洲三级视频网站| 97人人做人人爽香蕉精品| 在线精品视频小说1| 亚洲天堂av线| 日韩成人在线电影| 日韩欧美国产三级电影视频| 欧美一级大片免费看| 大奶在线精品| 亚洲精品国产成人| 精品少妇一区二区三区免费观| 你懂的视频欧美| 正在播放欧美视频| 欧美日韩午夜视频| 在线成人亚洲| 欧美专区在线观看| 亚洲视频在线免费播放| 国产主播一区二区三区| 国产福利久久| 玖玖综合伊人| 亚洲精品一二三四区| 日本a在线天堂| 在线成人av观看| 欧美揉bbbbb揉bbbbb| xxxxwww一片| 亚洲福利天堂| 一区二区视频欧美| 视频二区在线| 男人的天堂官网| 国产精品视频网站在线观看| 91av在线免费观看| 777色狠狠一区二区三区| 26uuu另类欧美| 欧美国产美女| 久久电影网站中文字幕| 久久久天堂国产精品女人| www.毛片.com| 美美哒免费高清在线观看视频一区二区| 成人国产精品久久久久久亚洲| 亚洲精品免费在线观看视频| 久久精品亚洲精品国产欧美| japanese在线播放| 在线成人av观看| 日韩免费视频线观看| wwwwww日本| 欧美日韩国产探花| 国产精品欧美日韩一区二区| 亚洲精品18在线观看| 国产日韩欧美综合一区| 亚洲小视频在线播放| 全亚洲第一av番号网站| 欧美大肚乱孕交hd孕妇| 亚洲图片另类小说| 激情久久婷婷| 成人字幕网zmw| 国产三级视频在线| 午夜精品久久久久影视| 肉色超薄丝袜脚交| 成人激情免费视频| 97成人超碰免| 亚洲成a人片在线| 中文字幕一区二区在线观看 | 91视频一区| 91成人福利在线| 亚洲黄色在线免费观看| 亚洲视频综合在线| 午夜视频在线瓜伦| 牲欧美videos精品| 久久免费成人精品视频| 国产成人三级一区二区在线观看一| 久久久久久久精| 国产精品宾馆在线精品酒店| 亚洲国产aⅴ精品一区二区| 视频在线观看99| 无码人妻精品一区二区蜜桃色欲| www.一区二区| 日本免费成人网| 精品国产一级| 伦理中文字幕亚洲| 国产女18毛片多18精品| 亚洲欧洲一区二区在线播放| 91制片厂毛片| 成人在线视频免费观看| 国产成+人+综合+亚洲欧美丁香花| 特黄视频在线观看| 午夜视频在线观看一区二区三区| 欧美久久久久久久久久久| 亚洲自拍偷拍网| 51国产成人精品午夜福中文下载 | 久久91麻豆精品一区| 9.1国产丝袜在线观看| 天天爽夜夜爽夜夜爽| 亚洲成人精品一区| av在线播放网址| 性久久久久久| 日本在线视频一区| 欧美一级做一级爱a做片性| 中文字幕亚洲字幕| 一级黄色大片网站| 亚洲欧美日韩国产另类专区| 国产探花在线观看视频| 欧美久久成人| 国产一区免费视频| 成人午夜视屏| 日日噜噜噜夜夜爽亚洲精品| 国产精品视频一二区| 亚洲精品一二三区| 欧美熟妇精品一区二区蜜桃视频| 亚洲理论在线| 日韩免费av电影| 亚洲久草在线| 久久久久久久久爱| 色在线免费视频| 欧美午夜免费电影| 紧身裙女教师波多野结衣| 成人综合在线网站| 无码aⅴ精品一区二区三区浪潮| 精品国产91久久久久久浪潮蜜月| 国产精品欧美一区二区| 羞羞视频在线观看不卡| 日韩精品在线视频美女| 成人小视频在线播放| 日韩美女精品在线| 久久性爱视频网站| 日本伊人午夜精品| 成人av在线不卡| 国产一区二区三区探花 | 国产欧美日韩成人| 亚洲二区视频在线| 少妇愉情理伦三级| 成人妖精视频yjsp地址| www.色就是色| 国产一区亚洲| 日韩av电影免费在线| 日韩精品成人在线观看| 琪琪第一精品导航| 超碰个人在线| 国产亚洲美女久久| 懂色av蜜臀av粉嫩av分享吧| 欧洲视频一区二区| 国产中文字幕免费| 国产精品美女一区二区| xxxx黄色片| 精品一区二区三区久久| 亚洲自偷自拍熟女另类| 亚洲乱码精品| 视频一区三区| 久久久久观看| 91老司机在线| 欧美性理论片在线观看片免费| 久久69精品久久久久久国产越南| 国产在线91| 亚洲国产成人av在线| 国产又粗又猛视频| 91高清在线观看| 国产精品suv一区二区三区| 亚洲男帅同性gay1069| 国产在线综合视频| 2017欧美狠狠色| 国产51自产区| 精彩视频一区二区| 91制片厂毛片| 日本不卡视频一二三区| 男女av免费观看| 在线成人黄色| 91午夜在线观看| 欧美在线国产| 日本一级淫片演员| 色乱码一区二区三区网站| 日本不卡久久| 群体交乱之放荡娇妻一区二区| 成人精品水蜜桃| 美女久久精品| 亚洲精品欧美日韩| 国模大尺度视频一区二区| 国产精品一区二区三区免费视频| 日本高清不卡一区二区三区视频| 2019亚洲男人天堂| 中文在线免费视频| 91av视频导航| 性欧美又大又长又硬| 91精品国产91久久久久久久久| 日韩激情av| 欧美激情手机在线视频| 日韩成人伦理| 久久久在线观看| 高清视频在线观看三级| 国自产精品手机在线观看视频| 色噜噜狠狠狠综合欧洲色8| 欧美国产日韩一区| 136福利第一导航国产在线| 欧美精品videos另类日本| 波多野结衣乳巨码无在线观看| 欧美国产在线电影| av在线理伦电影| 欧美性视频在线| 91精品影视| 国产日韩欧美综合| 欧美专区视频| 极品日韩久久| 国产成人精品999在线观看| 台湾成人av| 亚洲精品97| 你真棒插曲来救救我在线观看| 一本色道久久| 免费看黄色一级大片| 另类专区欧美蜜桃臀第一页| 日韩成人av免费| 丁香婷婷深情五月亚洲| 国产交换配乱淫视频免费| 国产欧美日韩三区| 翔田千里88av中文字幕| 亚洲大片免费看| 无码免费一区二区三区| 欧美日韩成人综合在线一区二区| a视频免费在线观看| 亚洲精品电影网| 91最新在线| 高清视频欧美一级| 成人视屏在线观看| 91福利视频导航| 亚洲香蕉视频| 国产手机视频在线观看| 亚洲日本国产| 免费看涩涩视频| 成人性色生活片免费看爆迷你毛片| 国产麻豆天美果冻无码视频| 国产精品久久久久久久久果冻传媒| 黄色片在线观看网站| 狠狠做深爱婷婷久久综合一区| 日本成人一级片| 精品国产123| 成人高清网站| 久久久久久久999精品视频| 福利一区和二区| 国产精品一区二区三区在线观| 精品无人区麻豆乱码久久久| 国产免费内射又粗又爽密桃视频| 亚洲欧美日本日韩| 中文字幕第66页| 久久精品一区八戒影视| 欧美成人国产精品高潮| 在线一区二区三区四区五区| 亚洲av无码国产精品久久不卡| 亚洲欧洲在线免费| 男女免费观看在线爽爽爽视频| 国产精品久久久久久久久借妻| 一区二区网站| 一区二区在线观| 久久中文欧美| 91精品又粗又猛又爽| 亚洲同性同志一二三专区| 欧美在线观看不卡| 精品福利一区二区三区免费视频| 18免费在线视频| 日韩av电影在线播放| 波多野结衣欧美| 天天操天天干天天玩| 日韩二区三区四区| 瑟瑟视频在线观看| 午夜精品在线视频一区| 成 人 免费 黄 色| 久久精品中文字幕| 免费成人高清在线视频| 热re99久久精品国99热蜜月| 亚洲看片一区| 免费看毛片的网站| 一区二区成人在线观看| av中文字幕播放| 久久久精品电影| 先锋影音网一区二区| 性欧美大战久久久久久久免费观看 | 国产传媒久久久| 国产成人午夜精品5599| 欧洲美女女同性互添| 欧美美女直播网站| 在线观看免费黄色| 国产精品一区二区三区久久| 日韩1区2区| 亚洲这里只有精品| 国产精品青草久久| 在线观看免费中文字幕| 在线免费观看羞羞视频一区二区| 波多视频一区| 日韩电影免费观看在| 视频一区二区国产| 2019男人天堂| 欧美系列亚洲系列| 三区四区在线视频| 91九色在线视频| 欧美在线看片| 亚洲精品无码一区二区| 亚洲国产裸拍裸体视频在线观看乱了 | 中文字幕在线中文字幕在线中三区| 国产一区二区高清不卡 | 欧美亚洲在线播放| 亚洲精华一区二区三区| 另类小说第一页| 国产精品对白交换视频 | 亚洲乱码一区av黑人高潮| 亚洲精品一区| 先锋影音网一区| 激情深爱一区二区| 免费在线黄色片| 日韩成人高清在线| 精品欧美一区二区三区在线观看 | 国产一区二区精品免费| 亚洲一区成人| 天堂av网手机版| 日韩一卡二卡三卡国产欧美| gogo高清午夜人体在线| 欧美精品v日韩精品v国产精品| 三级不卡在线观看| 日本黄色录像视频| 亚洲国产精品va在线看黑人动漫| 玖玖在线播放| 中文字幕一区二区三区在线乱码| 国产成人在线色| 国产成人一级片| 久久激情视频免费观看| 东京久久高清| av在线无限看| 一区二区三区在线观看动漫| 五月婷中文字幕| 91精品久久久久久综合乱菊 | 欧美视频久久久| 国产精品高潮呻吟久久av无限| 91精品国产麻豆国产在线观看| 美女黄色一级视频| 欧美午夜精品久久久| 婷婷av在线| 亚洲国产日韩欧美| 成人免费三级在线| 伊人网av在线| 久久理论片午夜琪琪电影网| 久久蜜桃av| 精品人妻一区二区三区香蕉| 欧美日韩国产免费一区二区 | 日本高清视频在线观看| 国产精品综合久久久久久| 六月丁香综合在线视频| 国产三级av片| 欧美大片免费观看| 日韩理论电影大全|