您的位置:首页 > 其它

Mocking

2015-09-12 10:42 274 查看

EasyMock

先定义一个特质

package com.oreilly.testingscala
trait DAO {
def persist[T](t: T)
}


使用EasyMock的静态方法createMock创建DAO的mock

package com.oreilly.testingscala
import org.scalatest.matchers.MustMatchers
import org.scalatest.Spec
import org.easymock.EasyMock._
class JukeboxStorageServiceEasyMockSpec extends Spec with MustMatchers {
describe("A Jukebox Storage Service") {
it("should use easy mock to mock out the DAO classes") {
val daoMock = createMock(classOf[DAO])
}
}
}


用stubs阻断真实数据库的调用,将数据存储到模拟数据库mock中。

package com.oreilly.testingscala
import org.scalatest.matchers.MustMatchers
import org.scalatest.Spec
import org.easymock.EasyMock._
class JukeboxStorageServiceEasyMockSpec extends FunSpec with MustMatchers {
describe("A Jukebox Storage Service") {
it("should use easy mock to mock out the DAO classes") {
// previous code removed for brevity

//set up actual values to be used.
val theGratefulDead: Band = new Band("Grateful Dead")
val wyntonMarsalis: Artist = new Artist("Wynton", "Marsalis")
val psychedelicFurs: Band = new Band("Psychedelic Furs")
val ericClapton: Artist = new Artist("Eric", "Clapton")

val workingmansDead = new Album("Workingman's Dead", 1970, None, theGratefulDead)
val midnightToMidnight = new Album("Midnight to Midnight", 1987, None, psychedelicFurs)
val wyntonAndClapton = new Album("Wynton Marsalis and Eric Clapton play the Blues", 2011, None, wyntonMarsalis, ericClapton)
val jukeBox = new JukeBox(Some(List(workingmansDead, midnightToMidnight, wyntonAndClapton)))
}
}
}
package com.oreilly.testingscala
import org.scalatest.matchers.MustMatchers
import org.scalatest.Spec
import org.easymock.EasyMock._
class JukeboxStorageServiceEasyMockSpec extends FunSpec with MustMatchers {
describe("A Jukebox Storage Service") {
it("should use easy mock to mock out the DAO classes") {
val daoMock = createMock(classOf[DAO])
//Code omitted for brevity

//create the subject under test
val jukeboxStorageService = new JukeboxStorageService(daoMock)
}
}
}


下面是调用。

package com.oreilly.testingscala
import org.scalatest.matchers.MustMatchers
import org.scalatest.Spec
import org.easymock.EasyMock._
class JukeboxStorageServiceEasyMockSpec extends Spec with MustMatchers {
describe("A Jukebox Storage Service") {
it("should use easy mock to mock out the DAO classes") {
//previous lines omitted for brevity

//set up expectations
albumMock.persist(workingmansDead)
albumMock.persist(midnightToMidnight)
albumMock.persist(wyntonAndClapton)

actMock.persist(theGratefulDead)
actMock.persist(psychedelicFurs)
actMock.persist(wyntonMarsalis)
actMock.persist(ericClapton)
}
}
}


EasyMock with ScalaTest

package com.oreilly.testingscala
import org.scalatest.Spec
import org.scalatest.matchers.MustMatchers
import org.scalatest.mock.EasyMockSugar
import org.easymock.EasyMock._
class JukeboxStorageServiceEasyMockWithSugarSpec extends Spec with MustMatchers
with EasyMockSugar {
describe("A Jukebox Storage Service") {
it("should use easy mock sugar in ScalaTest") {
val daoMock = createMock(classOf[DAO])

//set up actual values to be used.
val theGratefulDead: Band = new Band("Grateful Dead")
val wyntonMarsalis: Artist = new Artist("Wynton", "Marsalis")
val psychedelicFurs: Band = new Band("Psychedelic Furs")
val ericClapton: Artist = new Artist("Eric", "Clapton")

val workingmansDead = new Album("Workingman's Dead", 1970, None, theGratefulDead)
val midnightToMidnight = new Album("Midnight to Midnight", 1987, None, psychedelicFurs)
val wyntonAndClapton = new Album("Wynton Marsalis and Eric Clapton play the Blues", 2011, None, wyntonMarsalis, ericClapton)

val jukeBox = new JukeBox(Some(List(workingmansDead, midnightToMidnight, wyntonAndClapton)))

//create the subject under test
val jukeboxStorageService = new JukeboxStorageService(daoMock)

expecting {
daoMock.persist(workingmansDead)
daoMock.persist(midnightToMidnight)
daoMock.persist(wyntonAndClapton)
daoMock.persist(theGratefulDead)
daoMock.persist(psychedelicFurs)
daoMock.persist(wyntonMarsalis)
daoMock.persist(ericClapton)
}
whenExecuting(daoMock) {
jukeboxStorageService.persist(jukeBox)
}
}
}
}


Mockito

和Easymock类似,先创建mock,然后使用mock构建对象,最后用测试方法调用,验证。

package com.oreilly.testingscala
import org.specs2.Specification
import org.mockito.Mockito._
class JukeboxStorageServiceMockitoAcceptanceSpec extends Specification {
def is = {
"You can use Mockito to perform Scala mocking" ! useMockitoToMockClasses
}
def useMockitoToMockClasses = {
val daoMock = mock(classOf[DAO])
//set up actual values to be used.
val theGratefulDead: Band = new Band("Grateful Dead")
val wyntonMarsalis: Artist = new Artist("Wynton", "Marsalis")
val psychedelicFurs: Band = new Band("Psychedelic Furs")
val ericClapton: Artist = new Artist("Eric", "Clapton")
val workingmansDead = new Album("Workingman's Dead", 1970, None, theGratefulDead)
val midnightToMidnight = new Album("Midnight to Midnight", 1987, None, psychedelicFurs)
val wyntonAndClapton = new Album("Wynton Marsalis and Eric Clapton play the Blues", 2011, None, wyntonMarsalis, ericClapton)
val jukeBox = new JukeBox(Some(List(workingmansDead, midnightToMidnight, wyntonAndClapton)))
//create the subject under test
val jukeboxStorageService = new JukeboxStorageService(daoMock)
//no replay
//make the call
jukeboxStorageService.persist(jukeBox)
//verify that the calls expected were made
verify(daoMock).persist(theGratefulDead)
verify(daoMock).persist(workingmansDead)
success
}
}


ScalaMock

只有scala才能用,这个框架的目标是测试scala中一些比较难测试的点,比如:函数,单例,伴生对象,静态方法,最终对象,最终方法。ScalaMock还可以mock特质和具体类。使用ScalaMock时,只能与SBT一起工作,SBT是一个编译器插件,可以用generate-mocks创建mocks的字节码,然后使用它们。所以用ScalaMock时,你需要删除build.sbt文件,添加SBT插件,创建project/project/Build.scala和project/TestingScala.scala,第一个文件是定义编译器插件的,用于生成mocks,第二个是真实的build文件。如果不需要用generate-mocks去生成mock对象的话,可以不用删除build.sbt。

ScalaCheck

Scala Check是自动化测试,无需自己创建测试数据。两种方式反馈测试数据到ScalaTest。

TestNG,让最终用户创建数据并发送数据至测试方法。

Specs2有DataTable,允许开发者用测试数据创建一个类似ASCII码表,就好比一行一行的扔进测试。

ScalaCheck是根据参数的要求生成半随机的数据,这有时候会比人设计的测试案例好,因为人不太可能在完整的定义域上设计测试数据,一般情况下,ScalaCheck会比人设计的数据测试出来的代码更加的健壮。

ScalaCheck三个主要的组件

Properties,通过叫着Prop的一个测试套定义了测试并运行它们。

Gen,这个是一个生成器类,用于提供许多假数据,还允许我们去控制数据的类型。例如:你想产生正数,那么可以用Gen消除负数和0。

Arbitrary,用于自定义类型,程序中可能会有不是基本类型的类型。

Properties

package com.oreilly.testingscala
import org.scalacheck.{Prop, Properties}
object BasicScalaCheckProperties extends Properties("Simple Math"){
property("Sum is greater than its parts") = Prop.forAll {(x:Int, y:Int) => x+y > x && x+y > y}
}


性质就是不变的,所以是伴生对象,也就是单例,不能是类。上面的测试会失败,失败的测试,会有测试案例作为反馈。下面看一个成功的案例,加法交换律的测试

package com.oreilly.testingscala
import org.scalacheck.{Prop, Properties}
object BasicScalaCheckProperties extends Properties("Simple Math"){
property("Sums are associative") = Prop.forAll {(x:Int, y:Int) => x+y == y+x}
}


性质约束

一个人的姓名中,有first name, middle name, last name,有时候没有写middle name也是合法的,测试middle name就是一个Option,那么测试数据就应该有一部分是有middle name,有一部分是没有middle name的。

package com.oreilly.testingscala
import org.scalacheck.{Prop, Properties}
object ArtistScalaCheckProperties extends Properties("Testing Artists Thoroughly")
{
property("middleNames") =
Prop.forAll {
(firstName: String, middleName: Option[String], lastName: String) =>
middleName match {
case Some(x) =>
val artist = new Artist(firstName, x, lastName)
artist.fullName == firstName + " " + x + " " + lastName
case _ =>
val artist = new Artist(firstName, lastName)
artist.fullName == firstName + " " + lastName
}
}
}


在Scala Check测试当中,最好指定数据的编码为Unicode,否则会出现其他的编码,甚至乱码。

加入Gen产生数据。

object ArtistScalaCheckProperties extends Properties("Testing Artists Thoroughly")
{
property("middleNames") = Prop.forAll (Gen.alphaStr, Gen.oneOf(Gen.alphaStr.sample, None), Gen.alphaStr) {
(firstName: String, middleName: Option[String], lastName: String) =>
println(firstName, middleName, lastName)
middleName match {
case Some(x) =>
val artist = new Artist(firstName, x, lastName)
artist.fullName == firstName + " " + x + " " + lastName
case _ =>
val artist = new Artist(firstName, lastName)
artist.fullName == firstName + " " + lastName
}
}
}


多个性质

一个Properties对象能含有一个或者多个property测试。

package com.oreilly.testingscala
import org.scalacheck.{Gen, Prop, Properties}
import org.scalacheck.Prop._
object CombiningGenScalaCheckProperties extends Properties("Combining Properties")
{
val stringsOnly = Prop.forAll(Gen.alphaStr) {
x: String => (x != "") ==> x.size >= 0
}
val positiveNumbersOnly = Prop.forAll(Gen.posNum[Int]) {
x: Int => x >= 0
}
val positiveNumbers2Only = Prop.forAll(Gen.posNum[Int]) {
x: Int => x > 0
}
val alwaysPass = Prop.forAll {
x: Int => true
}
val wontPass = Prop.forAll((x: Int, y: Int) => x + y > 0)
property("And") = stringsOnly && positiveNumbersOnly
property("Or") = stringsOnly || wontPass
}


在一个数组中选择一个数用oneOf

Prop.forAll(Gen.oneOf(Gen.choose(-2, 3), Gen.value("Aretha Franklin"))) {
_ match {
case y: Int => (0 to 3).contains(y)
case z: String => z == "Aretha Franklin"
}
}


生成一个链表,长度为4,元素值介于20-60之间

Prop.forAll(Gen.listOfN(4, Gen.choose(20, 60))) {
x => (x.size == 4) && (x.sum < 240)
}


如果长度不限的话,用下面的listOf

Prop.forAll(Gen.listOf(Gen.choose(20, 60))) {
x =>
if (x.size > 0) x(0) > 19 && x(0) < 61
else true
}


classify用于显示测试案例的分布

Prop.forAll(Gen.listOf(Gen.choose(20, 60))) {
x =>
classify((x.size >= 0) && (x.size < 50), "0 to 50") {
classify((x.size >= 50) && (x.size < 100), "50 - 100") {
classify((x.size >= 100), "100 or more") {
true
}
}
}
}


listOf1表示至少一个元素

Prop.forAll(Gen.listOf1(Gen.choose(20, 60))) {
_.size > 0
}


containerOf用于产生随机长度的集合

Prop.forAll(Gen.containerOf[Set, Int](Gen.choose(1, 5))) {
x => true
}
Prop.forAll(Gen.containerOf1[Set, Int](Gen.choose(1, 5))) {
_.size > 0
}
Prop.forAll(Gen.containerOfN[Set, Int](4, Gen.choose(1, 5))) {
x => (x.size <= 4) && (x.sum < 240)
}


resultOf可以将一个函数做为生成器

Prop.forAll(Gen.resultOf((x: Int, y: String) => Map(x -> y))) {
p => println(p); p.isInstanceOf[Map[_,_]]
}


frequency指定频度

Prop.forAll(Gen.frequency(
(3, Gen.value("Phoenix")),
(2, Gen.value("LCD Soundsystem")),
(5, Gen.value("JJ")))) {
x =>
classify(x == "Phoenix", "Phoenix") {
classify(x == "LCD Soundsystem", "LCD Soundsystem") {
classify(x == "JJ", "JJ") {
true
}
}
}
}


SBT中还可以设置一些测试的属性

~test-only com.oreilly.testingscala.VariousGenCheckProperties -- -minSize 3 -maxSize 5


自定义生成器

假设没有Map,自定义一个Map

val gen = for {
x <- Gen.choose(3, 300)
y <- Gen.alphaStr
} yield Map(x -> y)

Prop.forAll(gen) {...}


或者

Gen.choose(3, 300).flatMap(x => Gen.alphaStr.map( y => Map(x -> y)))


还可以自定义生成对象

val albums = for {
a <- Gen.alphaStr
b <- Gen.choose(1900, 2012)
c <- Gen.alphaStr
} yield (new Album(a, b, new Band(c)))

val listOfAlbums = Gen.listOf(albums)
Prop.forAll(listOfAlbums) {
albums =>
val jukebox = new JukeBox(Some(albums))
jukebox.albums.get.size == albums.size
}


Arbitrary

Arbitrary用implicit变量和方法,所以无需添加,会隐式转换。

property("Arbitrary Creating Objects") = {
implicit val album: Arbitrary[Album] = Arbitrary {
for {
a <- Gen.alphaStr
b <- Gen.choose(1900, 2012)
c <- Gen.alphaStr
} yield (new Album(a, b, new Band(c)))
}

Prop.forAll {album: Album => album.ageFrom(2012) == (2012 - album.year)}
}


Labeling标签

下面的代码看不出是哪里出问题导致测试失败
Prop.forAll {
(x:Int, y:Int) => (x > 0 && y > 0) ==> {
(x + y) != 0 && (x+y) > 0 && (x+y) < (x+y)
}
}
添加标签后就知道哪里,其实就好比日志
Prop.forAll {
(x: Int, y: Int) => (x > 0 && y > 0) ==> {
((x + y) != 0) :| "(x+y) equals (y+x)" &&
((x+y) > 0) :| "(x+y) > 0" &&
((x+y) < (x+y)) :| "(x+y) < (x+y)"
}
}
让标签先出来,断言后出来,可以调整冒号的位置
Prop.forAll {
(x: Int, y: Int) => (x > 0 && y > 0) ==> {
("(x+y) equals (y+x)" |: ((x + y) != 0)) &&
("(x+y) > 0" |: ((x+y) > 0)) &&
("(x+y) < (x+y)" |: ((x+y) < (x+y)))
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: