本文是介绍提升测试完整度工具 的第二篇:mockito -- 一个源于java的测试框架。
mock测试在上篇已经安利了。这次用图来表示下,大家提味下mock的含义。
类A功能是对学生成绩进行排名,成绩需要学校的数据库。我们为了测试排名的功能,需要访问数据库,但这在测试过程时,并不是我们期望的。mockito帮助我们不查询数据库情况下,完成对功能进行测试。
见下图类A 功能实现依赖B,C.类B 依赖D。如果要测试A的功能,需要BC被调用,mock的功能就是在BC不被调用情况下,完成对A的测试。
如下图,使用mock工具是对类B/C处理,模拟BC提供的功能。通过模拟类的功能,让我们能够专心的构建测试场景,忽略外部依赖。
以上是mock基本使用思路。
mockito 介绍
在java测试框架领域,mockito绝对是大名鼎鼎。大多数的mock测试框架如EasyMock和ScalaMock都是expect-run-verify,也是我们上篇提到使用方式。而mockito拥有更简单、更直观api。mockito的设计理念是是尽量少的api,让用户更专注使用场景。
今天,我们重点是mockito版本scala版本(mockito-scala)。它延续了mockito的易用性,又基于scala订制更多的语法糖,让scala的开发者使用起来更加方便。接下来,我们一起走进mockito的世界。
用例运行环境
那句老话:一切没有预置前提说明文档都必然是耍流氓,_
//scala test inter scala mock sugar
// https://mvnrepository.com/artifact/org.mockito/mockito-scala
libraryDependencies += "org.mockito" %% "mockito-scala" % "1.5.16" % Test
// https://mvnrepository.com/artifact/org.mockito/mockito-scala-scalatest
libraryDependencies += "org.mockito" %% "mockito-scala-scalatest" % "1.5.16" % Test
本文用例基于 mockito-scala 1.5.16和scalatest 3.0.8的版本。
mockito 使用示例
我们使用了上一篇文档相同类进行测试,构造相同的场景,以便进行2个mock测试框架的异同。
mockito运行基于 scalatest,我们编写的用例形式上要遵循scalatest的形式。在介绍使用场景前,先看看mockito语法样式。
- mockito语法样式
// mock 是mockito关键字
//mock trait定义变量m1
val m1: MockObjectInTrait = mock[MockObjectInTrait]
//格式:条件函数 + 返回函数:如 when(m1.函数) thenReturn()
when(m1.getObjectInfo) thenReturn("mock info") //具体代码
从语法结构上看似乎比scalamock更好。
- 测试用例定义
class MyMockTestTest extends UnitSpec with MockitoSugar
mockito需要继承MockitoSugar
类才能运行以下测试用例。
mock函数
mockito不支持对函数的mock。这区别与scalamock.
基于trait
的mock
trait
进行mock操作关键字是:mock
.这点和scalamock是一致的。
源码如下:
trait MyMockTest {
def funcOnePara(name: String): String = {
name
}
def funcTwoPara(name: String, age:Int): String = name
def funcNoArgs: String = "func no args"
val valPara="variable param"
}
-
mock trait 示例
- mock
trait
无参数示例
val m = mock[MyMockTest]
when(m.funcNoArgs) thenReturn ("mockito mock") //调用无参时,返回指定字符串
println(m.funcNoArgs)
m.funcNoArgs //没有指定调用次数的接口when .. then..
用例编写风格,是不是比scalamock更易读哈。-
trait
有参调用mock
- mock
when(m.funcOnePara("xiaozhaoying")) thenReturn("mock zxy")
println(m.funcOnePara("xiaozhaoying")) //output mock zxy</pre>
基于class
的mock
和mocktrait
一样,class
mock是使用更多的一种场景。让我们来看看如何对class
进行mock操作
class
进行mock操作关键字是:mock
.
//源码示例
class MyMockClass {
var age: Int = 0
def funcMock = ""
def this(age:Int) { //辅助构造函数
this
this.age = age
}
}</pre>
-
class
mock操作示例
val mClass = mock[MyMockClass] //mock类
when(mClass.age) thenReturn(16) //设定变量值
println(mClass.age) //return 16
when(mClass.age) thenCallRealMethod() //调用真实值
println(mClass.age) //return 0
//mock 函数
when(mClass.funcMock) thenReturn ("func mock")
println(mClass.funcMock)//return func mock</pre>
从类的mock示例来看,我们看到: 1.mockito 支持对var变量的mock. 2.对函数mock方式看,类和trait没有区别 3.有新api- thenCallRealMethod ,即使mock变量/函数,依然能够调用真实实现。这在显示使用场景中减少代码量和实现难度。
基于case class
的mock
相比于scalamock对case class
mock的吃相难看, mockito更优雅。示例代码:
//
mock case class
val mockCase: Person = mock[Person]
when(mockCase.age) thenReturn (12)
println(mockCase.age)</pre>
我们看到和trait
,class
的mock方式并无二样。
同样,mockito不支持带参数类的mock.
基于class
mock其它应用
源码如下:
class MockClassInTrait {
def getArgs(name: String) = "with args"
def getArgs(name: String, age: Int): String = s"age"
def get(name: String, age: Int): String = s"age"
}
示例代码函数进行重载,对函数的mock使用上文方法是理所当然的支持。我们下面要看到的是mockito对函数重载提供的新特性。 示例:
val m1: MockClassInTrait = mock[MockClassInTrait]
//1
.对于任意参数,返回相同值
when(m1.getArgs(any[String])).thenReturn("mock with param")
println(m1.getArgs(""))
//2
.对于不同参数,返回不同值。对函数进行重写
when(m1.getArgs(any[String], any[Int])).thenAnswer((name: String, age: Int) => s"$name ")
println(m1.getArgs("then answer", 18))
//3.
对于不同参数,调用原始实现
when(m1.getArgs(any[String], any[Int])).thenCallRealMethod()
println(m1.getArgs("then answer", 18))</pre>
mockito涵盖函数调用的3种行为方式,让mock使用更加广泛和更具备可操作性。
部分mock场景支持的特性- spy
部分mock(partial mock)是说一个类的方法有些是实际调用,有些需要对函数进行mock.mockito给的解决方法是除了上文的thenCallRealMethod()
,还有就是spy
方式。 spy
方式提供不少新的特性,我们只简单描述提其基本特性。我们依然以MockClassInTrait
类为例看看,如何使用spy
特性。
val ms = spy(new MockClassInTrait, lenient = true)
println(ms.getArgs("")) //1.调用真实函数
//2.对函数进行mock
when(ms.getArgs("mock")).thenReturn("mock using spy")
println(ms.getArgs("mock"))
//3.调用真实函数
println(ms.getArgs(""))
//4.verify 校验ms变量函数执行次数
println(verify(ms, times(0)).getStr()) </pre>
其它特性
mockito在可用性上有更多值得我们发掘的功能,同性交友社区github的官方文档可参考。链接: https://github.com/mockito/mockito-scala
写在最后
不言而喻,我们借助工具提升代码质量。但付出的成本是不同的,可测试性代码或易测试的代码性价比更高。在我们提升代码质量措施上,我们心理都知道性价比最高的是哪一种?但项目决策远非软件工程所定义的那样。借用别人一句话:从统计的角度来看,大多数严格遵守软件工程理论的公司没产生多少杰出软件;大多数杰出软件和严格的遵守软件工程没什么关系。