欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品

主頁 > 知識庫 > Go語言Mock使用基本指南詳解

Go語言Mock使用基本指南詳解

熱門標簽:烏魯木齊人工電銷機器人系統 濮陽自動外呼系統代理 長沙ai機器人電銷 地圖標注測試 澳門防封電銷卡 智能電銷機器人營銷 賺地圖標注的錢犯法嗎 廣東語音外呼系統供應商 福州鐵通自動外呼系統

當前的實踐中問題

在項目之間依賴的時候我們往往可以通過mock一個接口的實現,以一種比較簡潔、獨立的方式,來進行測試。但是在mock使用的過程中,因為大家的風格不統一,而且很多使用minimal implement的方式來進行mock,這就導致了通過mock出的實現各個函數的返回值往往是靜態的,就無法讓caller根據返回值進行的一些復雜邏輯。

首先來舉一個例子

package task

type Task interface {
 Do(int) (string, error)
}

通過minimal implement的方式來進行手動的mock

package mock

type MinimalTask struct {
 // filed
}

func NewMinimalTask() *MinimalTask {
 return MinimalTask{}
}

func (mt *MinimalTask) Do(idx int) (string, error) {
 return "", nil
}

在其他包使用Mock出的實現的過程中,就會給測試帶來一些問題。

舉個例子,假如我們有如下的接口定義與函數定義

package pool

import "github.com/ultramesh/mock-example/task"

type TaskPool interface {
 Run(times int) error
}

type NewTask func() task.Task

我們基于接口定義和接口構造函數定義,封裝了一個實現

package pool

import (
 "fmt"
 "github.com/pkg/errors"
 "github.com/ultramesh/mock-example/task"
)

type TaskPoolImpl struct {
 pool []task.Task
}

func NewTaskPoolImpl(newTask NewTask, size int) *TaskPoolImpl {
 tp := TaskPoolImpl{
  pool: make([]task.Task, size),
 }
 for i := 0; i  size; i++ {
  tp.pool[i] = newTask()
 }
 return tp
}

func (tp *TaskPoolImpl) Run(times int) error {
 poolLen := len(tp.pool)
 for i := 0; i  times; i++ {
  ret, err := tp.pool[i%poolLen].Do(i)
  if err != nil {
   // process error
   return errors.Wrap(err, fmt.Sprintf("error while run task %d", i%poolLen))
  }
  switch ret {
  case "":
   // process 0
   fmt.Println(ret)
  case "a":
   // process 1
   fmt.Println(ret)
  case "b":
   // process 2
   fmt.Println(ret)
  case "c":
   // process 3
   fmt.Println(ret)
  }
 }
 return nil
}

接著我們來寫測試的話應該是下面

package pool

import (
 "github.com/golang/mock/gomock"
 "github.com/stretchr/testify/assert"
 "github.com/ultramesh/mock-example/mock"
 "github.com/ultramesh/mock-example/task"
 "testing"
)

type TestSuit struct {
 name string
 newTask NewTask
 size int
 times int
}

func TestTaskPoolRunImpl(t *testing.T) {

 testSuits := []TestSuit{
  {
   nam
  e: "minimal task pool",
   newTask: func() task.Task { return mock.NewMinimalTask() },
   size: 100,
   times: 200,
  },
 }

 for _, suit := range testSuits {
  t.Run(suit.name, func(t *testing.T) {
   var taskPool TaskPool = NewTaskPoolImpl(suit.newTask, suit.size)
   err := taskPool.Run(suit.size)
   assert.NoError(t, err)
  })
 }
}

這樣通過go test自帶的覆蓋率測試我們能看到TaskPoolImpl實際被測試到的路徑為

可以看到的手動實現MinimalTask的問題在于,由于對于caller來說,callee的返回值是不可控的,我們只能覆蓋到由MinimalTask所定死的返回值的路徑,此外mock在我們的實踐中往往由被依賴的項目來操作,他不知道caller怎樣根據返回值進行處理,沒有辦法封裝出一個簡單、夠用的最小實現供接口測試使用,因此我們需要改進我們mock策略,使用golang官方的mock工具——gomock來進行更好地接口測試。

gomock實踐

我們使用golang官方的mock工具的優勢在于

  • 我們可以基于工具生成的mock代碼,我們可以用一種更精簡的方式,封裝出一個minimal implement,完成和手工實現一個minimal implement一樣的效果。
  • 可以允許caller自己靈活地、有選擇地控制自己需要用到的那些接口方法的入參以及出參。

還是上面TaskPool的例子,我們現在使用gomock提供的工具來自動生成一個mock Task

mockgen -destination mock/mock_task.go -package mock -source task/interface.go

在mock包中生成一個mock_task.go來實現接口Task

首先基于mock_task.go,我們可以實現一個MockMinimalTask用于最簡單的測試

package mock

import "github.com/golang/mock/gomock"

func NewMockMinimalTask(ctrl *gomock.Controller) *MockTask {
 mock := NewMockTask(ctrl)
 mock.EXPECT().Do().Return("", nil).AnyTimes()
 return mock
}

于是這樣我們就可以實現一個MockMinimalTask用來做一些測試

package pool

import (
 "github.com/golang/mock/gomock"
 "github.com/stretchr/testify/assert"
 "github.com/ultramesh/mock-example/mock"
 "github.com/ultramesh/mock-example/task"
 "testing"
)

type TestSuit struct {
 name string
 newTask NewTask
 size int
 times int
}

func TestTaskPoolRunImpl(t *testing.T) {

 testSuits := []TestSuit{
  //{
  // name: "minimal task pool",
  // newTask: func() task.Task { return mock.NewMinimalTask() },
  // size: 100,
  // times: 200,
  //},
 {
   name: "mock minimal task pool",
   newTask: func() task.Task { return mock.NewMockMinimalTask(ctrl) },
   size: 100,
   times: 200,
  },
 }

 for _, suit := range testSuits {
  t.Run(suit.name, func(t *testing.T) {
   var taskPool TaskPool = NewTaskPoolImpl(suit.newTask, suit.size)
   err := taskPool.Run(suit.size)
   assert.NoError(t, err)
  })
 }
}

我們使用這個新的測試文件進行覆蓋率測試

可以看到測試結果是一樣的,那當我們想要達到更高的測試覆蓋率的時候應該怎么辦呢?我們進一步修改測試

package pool

import (
 "errors"
 "github.com/golang/mock/gomock"
 "github.com/stretchr/testify/assert"
 "github.com/ultramesh/mock-example/mock"
 "github.com/ultramesh/mock-example/task"
 "testing"
)

type TestSuit struct {
 name string
 newTask NewTask
 size int
 times int
 isErr bool
}

func TestTaskPoolRunImpl_MinimalTask(t *testing.T) {

 ctrl := gomock.NewController(t)
 defer ctrl.Finish()

 testSuits := []TestSuit{
  //{
  // name: "minimal task pool",
  // newTask: func() task.Task { return mock.NewMinimalTask() },
  // size: 100,
  // times: 200,
  //},
  {
   name: "mock minimal task pool",
   newTask: func() task.Task { return mock.NewMockMinimalTask(ctrl) },
   size: 100,
   times: 200,
  },
  {
   name: "return err",
   newTask: func() task.Task {
    mockTask := mock.NewMockTask(ctrl)
  // 加入了返回錯誤的邏輯
    mockTask.EXPECT().Do(gomock.Any()).Return("", errors.New("return err")).AnyTimes()
    return mockTask
   },
   size: 100,
   times: 200,
   isErr: true,
  },
 }

 for _, suit := range testSuits {
  t.Run(suit.name, func(t *testing.T) {
   var taskPool TaskPool = NewTaskPoolImpl(suit.newTask, suit.size)
   err := taskPool.Run(suit.size)
   if suit.isErr {
    assert.Error(t, err)
   } else {
    assert.NoError(t, err)
   }
  })
 }
}

這樣我們就能夠覆蓋到error的處理邏輯

甚至我們可以更trick的方式來將所有語句都覆蓋到,代碼中的testSuits改成下面這樣

package pool

import (
 "errors"
 "github.com/golang/mock/gomock"
 "github.com/stretchr/testify/assert"
 "github.com/ultramesh/mock-example/mock"
 "github.com/ultramesh/mock-example/task"
 "testing"
)

type TestSuit struct {
 name string
 newTask NewTask
 size int
 times int
 isErr bool
}

func TestTaskPoolRunImpl_MinimalTask(t *testing.T) {

 ctrl := gomock.NewController(t)
 defer ctrl.Finish()

 strs := []string{"a", "b", "c"}
 count := 0
 size := 3
 rounds := 1

 testSuits := []TestSuit{
  //{
  // name: "minimal task pool",
  // newTask: func() task.Task { return mock.NewMinimalTask() },
  // size: 100,
  // times: 200,
  //},
  {
   name: "mock minimal task pool",
   newTask: func() task.Task { return mock.NewMockMinimalTask(ctrl) },
   size: 100,
   times: 200,
  },
  {
   name: "return err",
   newTask: func() task.Task {
    mockTask := mock.NewMockTask(ctrl)
    mockTask.EXPECT().Do(gomock.Any()).Return("", errors.New("return err")).AnyTimes()
    return mockTask
   },
   size: 100,
   times: 200,
   isErr: true,
  },
  {
   name: "check input and output",
   newTask: func() task.Task {
    mockTask := mock.NewMockTask(ctrl)
  // 這里我們通過Do的設置檢查了mackTask.Do調用時候的入參以及調用次數
  // 通過Return來設置發生調用時的返回值
    mockTask.EXPECT().Do(count).Return(strs[count%3], nil).Times(rounds)
    count++
    return mockTask
   },
   size: size,
   times: size * rounds,
   isErr: false,
  },
 }
 var taskPool TaskPool
 for _, suit := range testSuits {
  t.Run(suit.name, func(t *testing.T) {
   taskPool = NewTaskPoolImpl(suit.newTask, suit.size)
   err := taskPool.Run(suit.times)
   if suit.isErr {
    assert.Error(t, err)
   } else {
    assert.NoError(t, err)
   }

  })
 }
}

這樣我們就可以覆蓋到所有語句

思考Mock的意義

之前和一些同學討論過,我們為什么要使用mock這個問題,發現很多同學的覺得寫mock的是約定好接口,然后在面向接口做開發的時候能夠方便測試,因為不需要接口實際的實現,而是依賴mock的Minimal Implement就可以進行單元測試。我認為這是對的,但是同時也覺得mock的意義不僅僅是如此。

在我看來,面向接口開發的實踐中,你應該時刻對接口的輸入和輸出保持敏感,更進一步的說,在進行單元測試的時候,你需要知道在給定的用例、輸入下,你的包會對起使用的接口方法輸入什么,調用幾次,然后返回值可能是什么,什么樣的返回值對你有影響,如果你對這些不了解,那么我覺得或者你應該去做更多地嘗試和了解,這樣才能盡可能通過mock設計出更多的單測用例,做更多且謹慎的檢查,提高測試代碼的覆蓋率,確保模塊功能的完備性。

Mock與設計模式

mock與單例

客觀來講,借助go語言官方提供的同步原語sync.Once,實現單例、使用單例是很容易的事情。在使用單例實現的過程中,單例的調用者往往邏輯中依賴提供的get方法在需要的時候獲取單例,而不會在自身的數據結構中保存單例的句柄,這也就導致我們很難類比前面介紹的case,使用mock進行單元測試,因為caller沒有辦法控制通過get方法獲取的單例。

既然是因為沒有辦法更改單例返回,那么解決這個問題最簡單的方式就是我們就應改提供一個set方法來設置更改單例。假設我們需要基于上面的case實現一個單例的TaskPool。假設我們定義了PoolImpl實現了Pool的接口,在創建單例的時候我們可能是這么做的(為了方便說明,這里我們用最早手工寫的基于MinimalTask來寫TaskPool的單例)

package pool

import (
 "github.com/ultramesh/mock-example/mock"
 "github.com/ultramesh/mock-example/task"
 "sync"
)

var once sync.Once
var p TaskPool

func GetTaskPool() TaskPool{
 once.Do(func(){
  p = NewTaskPoolImpl(func() task.Task {return mock.NewMinimalTask()},10)
 })
 return p
}

這個時候問題就來了,假設某個依賴于TaskPool的模塊中有這么一段邏輯

package runner

import (
 "fmt"
 "github.com/pkg/errors"
 "github.com/ultramesh/mock-example/pool"
)

func Run(times int) error {
 // do something
 fmt.Println("do something")

 // call pool
 p := pool.GetTaskPool()
 err := p.Run(times)
 if err != nil {
  return errors.Wrap(err, "task pool run error")
 }

 // do something
 fmt.Println("do something")
 return nil
}

那么這個Run函數的單測應該怎么寫呢?這里的例子還比較簡單,要是TaskPool的實現還要依賴一些外部配置文件,實際情形就會更加復雜,當然我們在這里不討論這個情況,就是舉一個簡單的例子。在這種情況下,如果單例僅僅只提供了get方法的話是很難進行解耦測試的,如果使用GetTaskPool勢必會給測試引入不必要的復雜性,我們還需要提供一個單例的實現者提供一個set方法來解決單元測試解耦的問題。將單例的實現改成下面這樣,對外暴露一個單例的set方法,那么我們就可以通過set方法來進行mock。

import (
 "github.com/ultramesh/mock-example/mock"
 "github.com/ultramesh/mock-example/task"
 "sync"
)

var once sync.Once
var p TaskPool

func SetTaskPool(tp TaskPool) {
 p = tp
}

func GetTaskPool() TaskPool {
 once.Do(func(){
  if p != nil {
   p = NewTaskPoolImpl(func() task.Task {return mock.NewMinimalTask()},10)
  }
  
 })
 return p
}

使用mockgen生成一個MockTaskPool實現

mockgen -destination mock/mock_task_pool.go -package mock -source pool/interface.go

類似的,基于前面介紹的思想我們基于自動生成的代碼實現一個MockMinimalTaskPool

package mock

import "github.com/golang/mock/gomock"

func NewMockMinimalTaskPool(ctrl *gomock.Controller) *MockTaskPool {
 mock := NewMockTaskPool(ctrl)
 mock.EXPECT().Run(gomock.Any()).Return(nil).AnyTimes()
 return mock
}

基于MockMinimalTaskPool和單例暴露出的set方法,我們就可以將TaskPool實現的邏輯拆除,在單測中只測試自己的代碼

package runner

import (
 "github.com/golang/mock/gomock"
 "github.com/stretchr/testify/assert"
 "github.com/ultramesh/mock-example/mock"
 "github.com/ultramesh/mock-example/pool"
 "testing"
)

func TestRun(t *testing.T) {

 ctrl := gomock.NewController(t)
 defer ctrl.Finish()

 p := mock.NewMockMinimalTaskPool(ctrl)

 pool.SetTaskPool(p)

 err := Run(100)
 assert.NoError(t, err)
}

到此這篇關于Go語言Mock使用基本指南詳解的文章就介紹到這了,更多相關Go語言Mock使用內容請搜索腳本之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:
  • 用gomock進行mock測試的方法示例
  • 使用Gomock進行單元測試的方法示例

標簽:廣西 德州 調研邀請 阿克蘇 西雙版納 貴陽 太原 慶陽

巨人網絡通訊聲明:本文標題《Go語言Mock使用基本指南詳解》,本文關鍵詞  語言,Mock,使用,基本,指南,;如發現本文內容存在版權問題,煩請提供相關信息告之我們,我們將及時溝通與處理。本站內容系統采集于網絡,涉及言論、版權與本站無關。
  • 相關文章
  • 下面列出與本文章《Go語言Mock使用基本指南詳解》相關的同類信息!
  • 本頁收集關于Go語言Mock使用基本指南詳解的相關信息資訊供網民參考!
  • 推薦文章
    欧美阿v视频在线大全_亚洲欧美中文日韩V在线观看_www性欧美日韩欧美91_亚洲欧美日韩久久精品
  • <rt id="w000q"><acronym id="w000q"></acronym></rt>
  • <abbr id="w000q"></abbr>
    <rt id="w000q"></rt>
    色国产综合视频| 亚洲一区在线观看视频| 全国精品久久少妇| 亚洲精品激情视频| 欧美日韩一区二区欧美激情 | 宅男噜噜噜66一区二区66| 亚洲欧美福利一区二区| 成人午夜短视频| 亚洲av鲁丝一区二区三区| 国产精品网站在线| 风间由美一区二区三区在线观看| 国产精品天天干| 精品久久久久久久久久久久久久久| 午夜精品福利久久久| 日本一级大毛片a一| 欧美人妖巨大在线| 亚洲chinese男男1069| 波多野结衣影院| 欧美日韩国产成人在线91| 亚洲一区二区三区在线| 亚洲少妇中文字幕| 91精品国产aⅴ一区二区| 日韩av午夜在线观看| 老鸭窝一区二区| 国产校园另类小说区| 国产成人综合视频| 国产女人被狂躁到高潮小说| 亚洲婷婷综合色高清在线| 99视频精品在线| 欧美网站一区二区| 日韩精品一二区| 日韩毛片无码永久免费看| 欧美国产97人人爽人人喊| 成人爱爱电影网址| 欧美日韩国产不卡| 美女脱光内衣内裤视频久久影院| 一级黄色毛毛片| 亚洲色图一区二区| 午夜免费福利影院| 久久这里只精品最新地址| 国产69精品一区二区亚洲孕妇| 在线免费精品视频| 石原莉奈在线亚洲三区| 亚洲精品成人av久久| 亚洲日本欧美天堂| 亚洲欧洲综合另类| 精品国产aⅴ一区二区三区东京热 久久久久99人妻一区二区三区 | 成人乱码一区二区三区av| 久久久精品国产免费观看同学| 风间由美一区二区三区在线观看 | 亚洲二区在线播放| 亚洲国产一区在线观看| 真实乱视频国产免费观看| 亚洲国产成人自拍| 人妻互换一二三区激情视频| 精品免费99久久| 成人av资源网站| 8x8x8国产精品| 国产毛片精品一区| 欧美日韩一区不卡| 国产做a爰片久久毛片| 在线观看亚洲一区| 久久精品72免费观看| 草视频在线观看| 蜜臀av性久久久久av蜜臀妖精| 91高清免费观看| 日日夜夜免费精品| 农村黄色一级片| 免播放器亚洲一区| av女名字大全列表| 免费精品视频在线| 色94色欧美sute亚洲线路一久| 日本欧美一区二区| 91极品视觉盛宴| 久久av中文字幕片| 欧美日韩色一区| 国产成人免费高清| 精品理论电影在线观看| 97se亚洲国产综合自在线不卡| 26uuu精品一区二区三区四区在线 26uuu精品一区二区在线观看 | 精品伦精品一区二区三区视频密桃| 亚洲精品视频一区| 手机av在线不卡| 午夜精品国产更新| 色吧成人激情小说| 国产乱码精品一区二区三区av| 欧美精品黑人性xxxx| 成人精品国产一区二区4080| 精品久久久久久无| 高清中文字幕mv的电影| 中文字幕在线不卡国产视频| 久久久久亚洲av成人无码电影| 亚洲午夜私人影院| 欧洲猛交xxxx乱大交3| 国产一区二区网址| 精品国产免费久久| 大桥未久恸哭の女教师| 亚洲欧美一区二区久久| 青青操在线播放| 久久99精品国产.久久久久久| 99久久免费精品| 国产精品久久免费看| 亚洲精品一区二区三区影院忠贞| 日韩精品一区第一页| 欧美色图12p| 99久久99久久精品免费观看 | 国产成人综合在线观看| 精品成人私密视频| aaaaaav| 午夜免费久久看| 欧美日韩视频在线观看一区二区三区| 成人午夜精品在线| 国产精品美女久久久久久| 欧美a在线播放| 精东粉嫩av免费一区二区三区| 日韩一级二级三级精品视频| 欧美性生交xxxxx| 午夜精品久久久久久久久久久| 欧美三级电影精品| 91丝袜呻吟高潮美腿白嫩在线观看| 综合在线观看色| 四虎免费在线视频| 不卡在线视频中文字幕| 国产精品麻豆一区二区| 91高清免费看| zzijzzij亚洲日本少妇熟睡| 国产精品私人自拍| 午夜国产福利一区二区| 成人精品亚洲人成在线| 国产精品你懂的在线| 999精品视频在线观看播放| 国产成人精品三级麻豆| 国产精品嫩草99a| 欧美性猛交xxxxx少妇| 91在线视频官网| 亚洲成人自拍偷拍| 日韩欧美中文字幕一区| 少妇真人直播免费视频| 久久99久久99小草精品免视看| www久久精品| 欧美乱大交做爰xxxⅹ小说| 国产成人在线看| 亚洲视频1区2区| 欧美日韩一区视频| 亚洲欧美色图视频| 韩国理伦片一区二区三区在线播放 | 国产 中文 字幕 日韩 在线| 日韩和欧美一区二区三区| 日韩精品一区二区在线| 丁香激情五月少妇| 成人在线综合网站| 亚洲精品久久久久久国产精华液| 欧美日韩精品一区二区三区蜜桃| 无码国产精品一区二区免费式直播 | 欧美日韩你懂得| 可以直接看的无码av| 经典三级在线一区| 自拍偷拍亚洲激情| 欧美日韩一二区| 扒开jk护士狂揉免费| 国产精品一区不卡| 亚洲另类春色校园小说| 911国产精品| 亚洲欧美综合7777色婷婷| 99久久国产综合精品麻豆| 水蜜桃久久夜色精品一区的特点| 精品国内二区三区| 9999热视频| 一本加勒比波多野结衣| 国产在线精品一区二区三区不卡| 国产精品国产成人国产三级| 欧美系列在线观看| 国产伦精品一区二区三区视频女| 成人涩涩免费视频| 日韩电影一二三区| 国产精品对白交换视频| 欧美酷刑日本凌虐凌虐| 国产精品国产三级国产专业不 | 大陆成人av片| 亚洲成av人片一区二区三区| 精品成人免费观看| 欧洲在线/亚洲| 成人片黄网站色大片免费毛片| 丰满白嫩尤物一区二区| 亚洲电影一区二区| 日本一区二区综合亚洲| 欧美日韩黄色影视| 亚洲欧美日韩第一页| 中文字幕乱妇无码av在线| 久久精品久久精品| 一区二区三区四区av| 2022国产精品视频| 欧美日韩小视频| 国产精品视频看看| 日韩www视频| 99久久国产综合精品色伊| 久久99久久久欧美国产| 樱花草国产18久久久久| 国产日韩欧美综合在线| 欧美一区二区女人|