最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

 更新時(shí)間:2020年06月11日 10:16:16   作者:Destiny池魚  
這篇文章主要介紹了Go語言Mock使用基本指南詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

當(dāng)前的實(shí)踐中問題

在項(xiàng)目之間依賴的時(shí)候我們往往可以通過mock一個(gè)接口的實(shí)現(xiàn),以一種比較簡(jiǎn)潔、獨(dú)立的方式,來進(jìn)行測(cè)試。但是在mock使用的過程中,因?yàn)榇蠹业娘L(fēng)格不統(tǒng)一,而且很多使用minimal implement的方式來進(jìn)行mock,這就導(dǎo)致了通過mock出的實(shí)現(xiàn)各個(gè)函數(shù)的返回值往往是靜態(tài)的,就無法讓caller根據(jù)返回值進(jìn)行的一些復(fù)雜邏輯。

首先來舉一個(gè)例子

package task

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

通過minimal implement的方式來進(jìn)行手動(dòng)的mock

package mock

type MinimalTask struct {
 // filed
}

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

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

在其他包使用Mock出的實(shí)現(xiàn)的過程中,就會(huì)給測(cè)試帶來一些問題。

舉個(gè)例子,假如我們有如下的接口定義與函數(shù)定義

package pool

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

type TaskPool interface {
 Run(times int) error
}

type NewTask func() task.Task

我們基于接口定義和接口構(gòu)造函數(shù)定義,封裝了一個(gè)實(shí)現(xiàn)

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
}

接著我們來寫測(cè)試的話應(yīng)該是下面

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自帶的覆蓋率測(cè)試我們能看到TaskPoolImpl實(shí)際被測(cè)試到的路徑為

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

gomock實(shí)踐

我們使用golang官方的mock工具的優(yōu)勢(shì)在于

  • 我們可以基于工具生成的mock代碼,我們可以用一種更精簡(jiǎn)的方式,封裝出一個(gè)minimal implement,完成和手工實(shí)現(xiàn)一個(gè)minimal implement一樣的效果。
  • 可以允許caller自己靈活地、有選擇地控制自己需要用到的那些接口方法的入?yún)⒁约俺鰠ⅰ?/li>

還是上面TaskPool的例子,我們現(xiàn)在使用gomock提供的工具來自動(dòng)生成一個(gè)mock Task

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

在mock包中生成一個(gè)mock_task.go來實(shí)現(xiàn)接口Task

首先基于mock_task.go,我們可以實(shí)現(xiàn)一個(gè)MockMinimalTask用于最簡(jiǎn)單的測(cè)試

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
}

于是這樣我們就可以實(shí)現(xiàn)一個(gè)MockMinimalTask用來做一些測(cè)試

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)
  })
 }
}

我們使用這個(gè)新的測(cè)試文件進(jìn)行覆蓋率測(cè)試

可以看到測(cè)試結(jié)果是一樣的,那當(dāng)我們想要達(dá)到更高的測(cè)試覆蓋率的時(shí)候應(yīng)該怎么辦呢?我們進(jìn)一步修改測(cè)試

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)
  // 加入了返回錯(cuò)誤的邏輯
    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的設(shè)置檢查了mackTask.Do調(diào)用時(shí)候的入?yún)⒁约罢{(diào)用次數(shù)
  // 通過Return來設(shè)置發(fā)生調(diào)用時(shí)的返回值
    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的意義

之前和一些同學(xué)討論過,我們?yōu)槭裁匆褂胢ock這個(gè)問題,發(fā)現(xiàn)很多同學(xué)的覺得寫mock的是約定好接口,然后在面向接口做開發(fā)的時(shí)候能夠方便測(cè)試,因?yàn)椴恍枰涌趯?shí)際的實(shí)現(xiàn),而是依賴mock的Minimal Implement就可以進(jìn)行單元測(cè)試。我認(rèn)為這是對(duì)的,但是同時(shí)也覺得mock的意義不僅僅是如此。

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

Mock與設(shè)計(jì)模式

mock與單例

客觀來講,借助go語言官方提供的同步原語sync.Once,實(shí)現(xiàn)單例、使用單例是很容易的事情。在使用單例實(shí)現(xiàn)的過程中,單例的調(diào)用者往往邏輯中依賴提供的get方法在需要的時(shí)候獲取單例,而不會(huì)在自身的數(shù)據(jù)結(jié)構(gòu)中保存單例的句柄,這也就導(dǎo)致我們很難類比前面介紹的case,使用mock進(jìn)行單元測(cè)試,因?yàn)閏aller沒有辦法控制通過get方法獲取的單例。

既然是因?yàn)闆]有辦法更改單例返回,那么解決這個(gè)問題最簡(jiǎn)單的方式就是我們就應(yīng)改提供一個(gè)set方法來設(shè)置更改單例。假設(shè)我們需要基于上面的case實(shí)現(xiàn)一個(gè)單例的TaskPool。假設(shè)我們定義了PoolImpl實(shí)現(xiàn)了Pool的接口,在創(chuàng)建單例的時(shí)候我們可能是這么做的(為了方便說明,這里我們用最早手工寫的基于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
}

這個(gè)時(shí)候問題就來了,假設(shè)某個(gè)依賴于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
}

那么這個(gè)Run函數(shù)的單測(cè)應(yīng)該怎么寫呢?這里的例子還比較簡(jiǎn)單,要是TaskPool的實(shí)現(xiàn)還要依賴一些外部配置文件,實(shí)際情形就會(huì)更加復(fù)雜,當(dāng)然我們?cè)谶@里不討論這個(gè)情況,就是舉一個(gè)簡(jiǎn)單的例子。在這種情況下,如果單例僅僅只提供了get方法的話是很難進(jìn)行解耦測(cè)試的,如果使用GetTaskPool勢(shì)必會(huì)給測(cè)試引入不必要的復(fù)雜性,我們還需要提供一個(gè)單例的實(shí)現(xiàn)者提供一個(gè)set方法來解決單元測(cè)試解耦的問題。將單例的實(shí)現(xiàn)改成下面這樣,對(duì)外暴露一個(gè)單例的set方法,那么我們就可以通過set方法來進(jìn)行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生成一個(gè)MockTaskPool實(shí)現(xiàn)

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

類似的,基于前面介紹的思想我們基于自動(dòng)生成的代碼實(shí)現(xiàn)一個(gè)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實(shí)現(xiàn)的邏輯拆除,在單測(cè)中只測(cè)試自己的代碼

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)
}

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

相關(guān)文章

  • 深入理解go緩存庫(kù)freecache的使用

    深入理解go緩存庫(kù)freecache的使用

    go開發(fā)緩存場(chǎng)景一般使用map或者緩存框架,為了線程安全會(huì)使用sync.Map或線程安全的緩存框架,本文就詳細(xì)的介紹了go緩存庫(kù)freecache,感興趣的可以了解一下
    2022-02-02
  • Go 并發(fā)讀寫 sync.map 詳細(xì)

    Go 并發(fā)讀寫 sync.map 詳細(xì)

    閱讀本文你將會(huì)明確 sync.Map 和原生 map +互斥鎖/讀寫鎖之間的性能情況。標(biāo)準(zhǔn)庫(kù) sync.Map 雖說支持并發(fā)讀寫 map,但更適用于讀多寫少的場(chǎng)景,因?yàn)樗麑懭氲男阅鼙容^差,使用時(shí)要考慮清楚這一點(diǎn)。
    2021-10-10
  • Golang 操作 Kafka 如何設(shè)置消息的失效時(shí)間

    Golang 操作 Kafka 如何設(shè)置消息的失效時(shí)間

    在使用 Golang 操作 Kafka 時(shí),你可以使用 Sarama 庫(kù)來設(shè)置消息的失效時(shí)間,這篇文章主要介紹了Golang操作Kafka設(shè)置消息的失效時(shí)間,需要的朋友可以參考下
    2023-06-06
  • 使用Go語言編寫一個(gè)簡(jiǎn)單的Web框架

    使用Go語言編寫一個(gè)簡(jiǎn)單的Web框架

    Go語言(又稱Golang)因其高效的性能和簡(jiǎn)潔的語法,在編寫Web框架方面表現(xiàn)出色,下面將詳細(xì)介紹如何使用Go語言編寫一個(gè)簡(jiǎn)單的Web框架,文中有詳細(xì)的代碼供大家參考,需要的朋友可以參考下
    2024-05-05
  • go語言string轉(zhuǎn)結(jié)構(gòu)體的實(shí)現(xiàn)

    go語言string轉(zhuǎn)結(jié)構(gòu)體的實(shí)現(xiàn)

    本文主要介紹了go語言string轉(zhuǎn)結(jié)構(gòu)體的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Golang?pprof性能測(cè)試與分析講解

    Golang?pprof性能測(cè)試與分析講解

    剛開始接觸go就遇到了一個(gè)內(nèi)存問題,在進(jìn)行內(nèi)存分析的時(shí)候發(fā)現(xiàn)了一下比較好的工具,在此留下記錄,下面這篇文章主要給大家介紹了關(guān)于go性能分析工具pprof的性能測(cè)試,需要的朋友可以參考下
    2023-04-04
  • golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn)

    golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn)

    本文主要介紹了golang Iris運(yùn)行多個(gè)應(yīng)用的實(shí)現(xiàn),在Iris里面,提供了一種方式可以讓我們同時(shí)運(yùn)行多個(gè)應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-01-01
  • Go語言開發(fā)框架反射機(jī)制及常見函數(shù)示例詳解

    Go語言開發(fā)框架反射機(jī)制及常見函數(shù)示例詳解

    這篇文章主要為大家介紹了Go語言開發(fā)框架反射機(jī)制及常見函數(shù)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • Go語言實(shí)現(xiàn)Sm2加解密的示例代碼

    Go語言實(shí)現(xiàn)Sm2加解密的示例代碼

    本文主要介紹了Go語言實(shí)現(xiàn)Sm2加解密的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-03-03
  • Golang中接收者方法語法糖的使用方法詳解

    Golang中接收者方法語法糖的使用方法詳解

    這篇文章主要為大家詳細(xì)介紹了Golang中接收者方法語法糖的使用方法,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下
    2023-05-05

最新評(píng)論

罗山县| 甘泉县| 金川县| 乌兰浩特市| 六枝特区| 盐山县| 邯郸市| 根河市| 赞皇县| 时尚| 奉化市| 武平县| 永修县| 苏尼特左旗| 星子县| 巴林右旗| 铁力市| 灵璧县| 通许县| 栾川县| 二手房| 马关县| 光泽县| 屯门区| 淮阳县| 南皮县| 泸定县| 安泽县| 富宁县| 任丘市| 林州市| 曲松县| 鹤山市| 宿州市| 江孜县| 准格尔旗| 怀化市| 吴堡县| 库伦旗| 寿光市| 东兴市|