中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go怎么對數據庫CRUD進行Mock測試

發布時間:2022-06-22 09:39:53 來源:億速云 閱讀:144 作者:iii 欄目:開發技術

今天小編給大家分享一下Go怎么對數據庫CRUD進行Mock測試的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

go-sqlmock

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數據庫操作進行測試。

miniredis

除了經常用到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測試”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

安丘市| 韩城市| 定西市| 固安县| 博白县| 佛坪县| 浦北县| 枣阳市| 隆化县| 巴青县| 石台县| 油尖旺区| 佛坪县| 勃利县| 盐亭县| 保德县| 桐柏县| 千阳县| 亳州市| 酒泉市| 盐城市| 沐川县| 墨竹工卡县| 兰西县| 进贤县| 舒城县| 故城县| 闵行区| 沛县| 宁武县| 黄大仙区| 庆阳市| 盐边县| 冕宁县| 台州市| 中西区| 仙桃市| 盘山县| 罗平县| 桂林市| 枣强县|