注意:本文大部份内容为翻译 Bob 大叔的文章,原文连接能够在文章底部的参考文档处找到。html
mock 做为名词时表示 mock 对象,在维基百科的解释中以下:程序员
在面向对象程序设计中,模拟对象(英语:mock object,也译做模仿对象)是以可控的方式模拟真实对象行为的假的对象。程序员一般创造模拟对象来测试其余对象的行为。web
mock 做为动词时表示编写使用 mock 对象。数据库
mock 多用于测试代码中,对于不容易构造或者不容易获取的对象,使用一个虚拟的对象来方便测试。编程
为了使用示例说明各个mock 种类的区别与联系,文章使用 go 语言做为示例,以下为示例的基础代码:安全
type Authorizer interface {
authorize(username, password string) bool
}
type System struct {
authorizer Authorizer
}
func NewSystem(authorizer Authorizer) *System {
system = new(System)
system.authorizer = authorizer
return system
}
func (s *System) loginCount() int {
// skip
return 0
}
func (s *System) login(username, password string) error {
if s.authorizer.authorize(username, password) {
return nil
}
return errors.New("username or password is not right")
}
复制代码
当你不关心传入的参数被如何使用时,你就应该使用 dummy 类型的 mock,通常用于做为其余对象的初始化参数。示例以下:服务器
type DummyAuthorizer struct {}
func (d *DummyAuthorizer) authorize(username, password string) bool {
// return nil
return false
}
// Test
func TestSystem(t *testing.T) {
system := NewSystem(new(DummyAuthorizer))
got := system.loginCount()
want := 0
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
复制代码
在上面的测试示例代码中,DummyAuthorizer 的做为只是为了初始化 System 对象的须要,后续测试中并无使用该 DummyAuthorizer 对象。网络
注意:此处的 authorize 方法原文返回了 null ,因为 go 语言不容许为 bool 返回 nil ,所以此处返回了 false架构
当你只关心方法的返回结果,而且须要特定返回值的时候,这时候你就可使用 stub 类型的 mock 。好比咱们须要测试系统中某些功能是否能正确处理用户登陆和不登陆的状况,而登陆功能咱们已经在其余地方通过测试,并且使用真实的登陆功能调用又比较的麻烦,咱们就能够直接返回已登陆或者未登陆状态来进行其余功能的验证。函数
type AcceptingAuthorizerStub struct {}
func (aas *AcceptingAuthorizerStub) authorize(username, password string) bool {
return true
}
type RefusingAuthorizerStub struct {}
func (ras *RefusingAuthorizerStub) authorize(username, password string) bool {
return false
}
复制代码
当你不仅是只关心方法的返回结果,还须要检查方法是否真正的被调用了,方法的调用次数等,或者须要记录方法调用过程当中的信息。这个时候你就应该使用 spy 类型的 mock ,调用结束后你须要本身检查方法是否被调用,检查调用过程当中记录的其余信息。可是请注意,这将会使你的测试代码和被测试方法相耦合,测试须要知道被测试方法的内部实现细节。使用时须要谨慎一些,不要过渡使用,过渡使用可能致使测试过于脆弱。
type AcceptingAuthorizerSpy struct {
authorizeWasCalled bool
}
func (aas *AcceptingAuthorizerSpy) authorize(username, password string) bool {
aas.authorizeWasCalled = true
return true
}
// Test
func TestSystem(t *testing.T) {
authorizer := new(AcceptingAuthorizerSpy)
system := NewSystem(authorizer)
got := system.login("will", "will")
if got != nil {
t.Errorf("login failed with error %v", got)
}
if authorizer.authorizeWasCalled != true {
t.Errorf("authorize was not called")
}
}
复制代码
mock 类型的 mock 能够算做是真正的 ”mock“ 。把 spy 类型的 mock 在测试代码中的断言语句移动到 mock 对象中,这使它更关注于测试行为。这种类型的 mock 对方法的返回值并非那么的感兴趣,它更关心的是哪一个方法被使用了什么参数在什么时间被调用了,调用的频率等。这种类型的 mock 使得编写 mock 相关的工具更加的简单,mock 工具能够帮助你在运行时建立 mock 对象。
type AcceptingAuthorizerVerificationMock struct {
authorizeWasCalled bool
}
func (aavm *AcceptingAuthorizerVerificationMock) authorize(username, password string) bool {
aavm.authorizeWasCalled = true
return true
}
func (aavm *AcceptingAuthorizerVerificationMock) verify() bool {
return aavm.authorizeWasCalled
}
复制代码
fake 类型的 mock 与其余类型的 mock 最大的区别是它包含了真实的业务逻辑。当以不一样的数据调用时,你会获得不一样的结果。随着业务逻辑的改变,它可能也会愈来愈复杂,最终你也须要为这种类型的 mock 编写单元测试,甚至最后它可能成为了一个真实的业务系统。若是不是必须,请不要使用 fake 类型的 mock 。
type AcceptingAuthorizerFake struct {}
func (aas *AcceptingAuthorizerFake) authorize(username, password string) bool {
if username == "will" {
return true
}
return false
}
复制代码
mock 是 spy 的一种类型,spy 又是 stub 的一种类型,而 stub 又是 dummy 的一种类型,可是 fake 与其余全部 mock 类型不一样,fake 包含了真实的业务逻辑,而其余类型的 mock 都不包含真实的业务逻辑。
根据 Bob 大叔的实践来看,他使用最多的是 spy 和 stub 类型的 mock ,而且他不会常用 mock 工具,不多使用 dummy 类型的 mock ,只有在使用 mock 工具时才会使用 mock 类型的 mock 。如今的编程 IDE 中,只须要你定义好接口,IDE 就能够帮你轻松的实现他们,你只须要简单的修改就能够实现 spy 和 stub 类型的 mock ,所以 Bob 大叔不多使用 mock 工具。
mock 对象是一个强大的工具,可是 mock 对象也有两面性,若是使用不正确也可能会带来强大的破坏力。
若是咱们彻底不使用 mock ,直接使用真实的对象进行测试,这会带来什么问题呢?
在彻底不使用 mock 对象的状况下,咱们的测试会变得缓慢、不完整、脆弱。
若是过分使用 mock 对象,全部的测试都使用 mock 对象,这会带来什么问题呢?
过分使用 mock 对象,将会使用测试变得缓慢、脆弱、复杂,而且有可能损坏你的软件设计。
在架构的重要边界使用 mock ,不要在边界内部使用 mock
例如能够在数据库、web服务器等全部第三方服务的边界处使用 mock 。能够参考以下的整洁架构图:
能够在最外环的边界处使用 mock 隔离外部依赖,方便测试,这样作能够获得以下的好处:
另外一个比较大的好处是它强迫你思考找出软件的重要边界,而且为它们定义接口,这使得你的软件不会强耦合依赖于边界外的组件。所以你能够独立开发部署边界两边的组件。像这样去分离架构关注点是一个很好的软件设计原则。
使用你本身的 mock
mock 工具备它们本身的领域语言,在使用它们以前你必须先学习它。经过前面的 mock 类型介绍,咱们已经知道用的最多的 mock 是 stub 和 spy 类型,而因为如今的 IDE 能够很方便的生成这些 mock 代码,咱们只须要稍做修改就能够直接使用,因此综合来看,咱们通常状况下是不须要使用 mock 工具的。
因为你本身写 mock 时不会使用反射,这将会让你的测试代码运行速度更快。若是你决定使用 mock 工具,请尽可能少的使用它。
mock 对象既不能彻底不使用,也不能过分使用。咱们应该在软件的重要边界处使用 mock ,要尽可能少的使用 mock 工具,使用 mock 工具时不要过分依赖它,咱们应该尽可能使用轻量级的 stub 和 spy 的 mock 类型,而且咱们应该本身手写这些简单的 mock 类型。若是你这样作了,你会发现你的测试运行速度更快,更稳定,而且还会有更高的测试覆盖率,你的软件架构设计也会愈来愈好。