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,自定义一个Mapval 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))) } }
相关文章推荐
- 人肉工程在机器学习实践中的作用
- C++基础之const系列
- HDOJ 1009
- 7.性能测试工具Locust的初级使用
- OC第四课
- ChartType属性
- iOS开发系列--Objective-C之类和对象
- html css 图片居中
- javascript闭包(Closure)初探
- 注意uiview的两个重绘函数
- UI中一系列 个人错误总结
- 你应该掌握的七种回归技术
- JDK 1.7源码阅读笔记(七)集合类之HashMap
- (标记)深入理解javascript作用域和闭包
- 几款开源的图形化Redis客户端管理软件
- win10配置java环境变量,解决javac不是内部或外部命令等问题
- Leetcode: Wiggle Sort
- 配置Tomcat使用https协议(配置SSL协议)
- UVa 12558:Egyptian Fractions (HARD version)(IDA*)
- 【JUnit】@Test 报错,"Test cannot be resolved to a type"