mockito- 提升用例完整度利器(2)

本文是介绍提升测试完整度工具 的第二篇:mockito -- 一个源于java的测试框架

mock测试在上篇已经安利了。这次用图来表示下,大家提味下mock的含义。

  • 类A功能是对学生成绩进行排名,成绩需要学校的数据库。我们为了测试排名的功能,需要访问数据库,但这在测试过程时,并不是我们期望的。mockito帮助我们不查询数据库情况下,完成对功能进行测试。

  • 见下图类A 功能实现依赖B,C.类B 依赖D。如果要测试A的功能,需要BC被调用,mock的功能就是在BC不被调用情况下,完成对A的测试。

mock-before.PNG

如下图,使用mock工具是对类B/C处理,模拟BC提供的功能。通过模拟类的功能,让我们能够专心的构建测试场景,忽略外部依赖。

mock-after.PNG

以上是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.16scalatest 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

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>

  • classmock操作示例

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 classmock的吃相难看, 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"name +age"

def get(name: String, age: Int): String = s"name +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

写在最后

不言而喻,我们借助工具提升代码质量。但付出的成本是不同的,可测试性代码或易测试的代码性价比更高。在我们提升代码质量措施上,我们心理都知道性价比最高的是哪一种?但项目决策远非软件工程所定义的那样。借用别人一句话:从统计的角度来看,大多数严格遵守软件工程理论的公司没产生多少杰出软件;大多数杰出软件和严格的遵守软件工程没什么关系。

各位看官大大,赏个赞撒。

推荐阅读更多精彩内容