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有两种方法来产生模拟函数:
- 解析Go文件源代码。
pegomock generate [<flags>] <gofile> - 构建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。
1595

被折叠的 条评论
为什么被折叠?



