PegoMock介绍

前言

PegoMock是一个Go语言上的一个mock框架。它能够很好地兼容Go语言内建的testing程序包,另外也能在其他环境下使用。它基于golang/mock,但是使用了类似于Mockito的DSL。

获取PegoMock

只需要go get它:
go get github.com/petergtz/pegomock/...
这样就可以下载程序包,在$GOPATH/bin安装可执行的pegomock

开始使用

在Go语言中xUnit-风格的测试中使用Pegomock

推荐的使用方式:

import (
	"github.com/petergtz/pegomock"
	"testing"
)

func TestUsingMocks(t *testing.T) {
	mock := NewMockPhoneBook(pegomock.WithT(t))

	// use your mock here
}

另外,你也可以在测试总设置全局的失败handler。

func TestUsingMocks(t *testing.T) {
	pegomock.RegisterMockTestingT(t)

	mock := NewMockPhoneBook()

	// use your mock here
}

备注:在这个例子中,Pegomock使用了一个全局失败handler。这可以让你避免层层传递失败handler,但是也意味着你不能并行运行这些XUnit风格测试。

如果同时存在全局handler和针对mock的handler,那么针对mock的handler会覆盖全局的handler。

生成和使用第一个mock

假设你有:

type Display interface {
	Show(text string)
}

最简单的方法就是在程序包的路径下调用命令pegomock,指定被mock的接口名:

cd path/to/package
pegomock generate Display

这就可以产生一个mock_display_test.go文件,可以在测试中使用:

// creating mock
display := NewMockDisplay()

// using the mock
display.Show("Hello World!")

// verifying
display.VerifyWasCalledOnce().Show("Hello World!")

为什么不选择其他的mock框架

  • GoMock的mock操作和验证操作较为繁琐。命令行接口不是特别直观。同时,Pegomock是基于GoMock,复用了大部分的mock生成代码。
  • Counterfeiter使用了半成品的DSL。代码也较为繁琐。例如Counterfeiter如下
 fake.DoThings("stuff", 5)
 Expect(fake.DoThingsCallCount()).To(Equal(1))

 str, num := fake.DoThingsArgsForCall(0)
 Expect(str).To(Equal("stuff"))
 Expect(num).To(Equal(uint64(5)))

在Pegomock可以较为简单:

 fake.DoThings("stuff", 5)
 fake.VerifyWasCalledOnce().DoThings("stuff", 5)
  • Hel使用了新颖和有趣的方式来设置和检验mock,但是灵活性存疑。
    此外,Pegomock提供了类似于Ginkgo中的“watch”命令,可以实时检测代码变动并更新mock代码。

在单测中使用mock

验证方法行为

接口:

type Display interface {
	Show(text string)
}

测试:

// creating mock:
display := NewMockDisplay()

// using the mock:
display.Show("Hello World!")

// verifying:
display.VerifyWasCalledOnce().Show("Hello World!")

替换代码片段(桩)

接口:

type PhoneBook interface {
	GetPhoneNumber(name string) string
}

测试:

// creating the mock
phoneBook := NewMockPhoneBook()

// stubbing:
When(phoneBook.GetPhoneNumber("Tom")).ThenReturn("345-123-789")
When(phoneBook.GetPhoneNumber("Invalid")).ThenPanic("Invalid Name")

// prints "345-123-789":
fmt.Println(phoneBook.GetPhoneNumber("Tom"))

// panics:
fmt.Println(phoneBook.GetPhoneNumber("Invalid"))

// prints "", because GetPhoneNumber("Dan") was not stubbed
fmt.Println(phoneBook.GetPhoneNumber("Dan"))

// Although it is possible to verify a stubbed invocation, usually it's redundant
// If your code cares what GetPhoneNumber("Tom") returns, then something else breaks (often even before a verification gets executed).
// If your code doesn't care what GetPhoneNumber("Tom") returns, then it should not be stubbed.

// Not convinced? See http://monkeyisland.pl/2008/04/26/asking-and-telling.
phoneBook.VerifyWasCalledOnce().GetPhoneNumber("Tom")
  • 默认情况下,所有存在返回值的方法会返回零值。
  • 代码片段一旦替换,方法必会返回相应的值,无论被调用多少次。
  • ThenReturn支持链式调用,例如ThenReturn(...).ThenReturn(...)。模仿的函数会按照串联顺序依次返回响应的值。一旦调用次数超过链长,该函数会一直返回最后一个值。

替换没有返回值的函数

替换没有返回值的函数方式略微有点不同,因为这样的函数无法直接传递给另外一个函数。不过,我们可以把它包装在一个无名函数中。

// creating mock:
display := NewMockDisplay()

// stubbing
When(func() { display.Show("Hello World!") }).ThenPanic("Panicking")

// panics:
display.Show("Hello World!")

实参验证

Pegomock给代码替换和验证提供了匹配功能。
验证:

display := NewMockDisplay()

// Calling mock
display.Show("Hello again!")

// Verification:
display.VerifyWasCalledOnce().Show(AnyString())

代码替换:

phoneBook := NewMockPhoneBook()

// Stubbing:
When(phoneBook.GetPhoneNumber(AnyString())).ThenReturn("123-456-789")

// Prints "123-456-789":
fmt.Println(phoneBook.GetPhoneNumber("Dan"))
// Also prints "123-456-789":
fmt.Println(phoneBook.GetPhoneNumber("Tom"))

重要:如果你使用了实参匹配器,你就要对所有的实参使用匹配器。

// 错误, panics:
When(contactList.getContactByFullName("Dan", AnyString())).thenReturn(Contact{...})
// 正确:
When(contactList.getContactByFullName(EqString("Dan"), AnyString())).thenReturn(Contact{...})

验证调用次数

display := NewMockDisplay()

// Calling mock
display.Show("Hello")
display.Show("Hello, again")
display.Show("And again")

// Verification:
display.VerifyWasCalled(Times(3)).Show(AnyString())
// or:
display.VerifyWasCalled(AtLeast(3)).Show(AnyString())
// or:
display.VerifyWasCalled(Never()).Show("This one was never called")

按照次序验证

display1 := NewMockDisplay()
display2 := NewMockDisplay()

// Calling mocks
display1.Show("One")
display1.Show("Two")
display2.Show("Another two")
display1.Show("Three")

// Verification:
inOrderContext := new(InOrderContext)
display1.VerifyWasCalledInOrder(Once(), inOrderContext).Show("One")
display2.VerifyWasCalledInOrder(Once(), inOrderContext).Show("Another two")
display1.VerifyWasCalledInOrder(Once(), inOrderContext).Show("Three")

InOrderContext只验证那些已经被调用的情况。

利用回调函数进行替换

phoneBook := NewMockPhoneBook()

// Stubbing:
When(phoneBook.GetPhoneNumber(AnyString())).Then(func(params []Param) ReturnValues {
	return []ReturnValue{fmt.Sprintf("1-800-CALL-%v", strings.ToUpper(params[0]))}
},


// Prints "1-800-CALL-DAN":
fmt.Println(phoneBook.GetPhoneNumber("Dan"))
// Prints "1-800-CALL-TOM":
fmt.Println(phoneBook.GetPhoneNumber("Tom"))

利用参数捕捉进行验证

在某些情况下,我们需要模拟调用,分别验证调用参数。这种方法只适用于实参匹配器无能为力的情形。

display := NewMockDisplay()

// Calling mock
display.Show("Hello")
display.Show("Hello, again")
display.Show("And again")

// Verification and getting captured arguments
text := display.VerifyWasCalled(AtLeast(1)).Show(AnyString()).GetCapturedArguments()

// Captured arguments are from last invocation
Expect(text).To(Equal("And again"))

你也能获得所有的实参:

// Verification and getting all captured arguments
texts := display.VerifyWasCalled(AtLeast(1)).Show(AnyString()).GetAllCapturedArguments()

// Captured arguments are a slice
Expect(texts).To(ConsistOf("Hello", "Hello, again", "And again"))

用异步的模仿调用进行验证

在Goroutine中,我们有时候需要用VerifyWasCalledEventually来对某个时间段内的函数调用进行判断。

display := NewMockDisplay()

go func() {
	doSomething()
	display.Show("Hello")
}()

display.VerifyWasCalledEventually(Once(), 2*time.Second).Show("Hello")

Pegomock命令行

安装

安装:

go install github.com/petergtz/pegomock/pegomock

生成Mocks

Pegomock有两种方法来产生模拟函数:

  1. 解析Go文件源代码。pegomock generate [<flags>] <gofile>
  2. 构建Go程序包,使用reflecting。pegomock generate [<flags>] [<packagepath>] <interfacename>
    flags可以是以下任意一个:
  • --output, -o:输出文件,默认是mock_..._test.go
  • --package:生成代码所使用的package,默认是源代码的package加上_test后缀。
  • --generate-marchers, -m:这会自动生成参数匹配器,放在matchers文件夹下。

go generate生成模拟函数

pegomock可以和go generate一起使用。它会在你的源文件边上加上个文件夹。
这里有一个例子,Display接口被calculator程序使用。

// package/path/to/display/display.go

package display

type Display interface {
	Show(text string)
}
// package/path/to/calculator/calculator_test.go

package calculator_test

//go:generate pegomock generate package/path/to/display Display

// Use generated mock
mockDisplay := NewMockDisplay()
...

生成:

cd package/path/to/calculator
go generate

注意:建议在调用interface的代码处来生成模拟函数。

持续生成模拟函数

watch可以让Pegomock持续为代码上的每一次改动生成模拟函数。

pegomock watch

模拟函数的代码会持续添加到interfaces_to_mock文件中。mocks的代码如下所示:

# Any line starting with a # is treated as comment.

# interface name without package specifies an Interface in the current package:
PhoneBook

 # generates a mock for SomeInterfacetaken from mypackage:
path/to/my/mypackage SomeInterface

# you can also specify a Go file:
display.go

# and use most of the flags from the "generate" command
--output my_special_output.go MyInterface

flags可以是:

  • --recursive, -r:递归地监控子文件夹。

移除生成的模拟函数

pegomock remove

这个命令可以系统性移除生成模拟函数和参数匹配器代码。它也支持递归移除:--recursive, -r

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值