ScalaTest中的Matchers匹配器
分为两大类:Should Matcher
和Must Matcher
。
它们的区别仅在测试报告中体现,所以在后面不同情形下的Matcher的说明中,只以Should Matcher为例。
3.1 简单匹配器
Simple Matcher就是在两个值之间使用一个断言
在测试代码里,我们作出了一个断言:Thriller这张专辑的作者的firstName是Michael。可以看到代码有一个should,这就是ScalaTest中的两大类Matcher之一的Should Matcher
(另一类是Must Matcher,用must来表示)
import org.scalatest.{FunSpec, Matchers}
class Artist(val firstName:String,val lastName:String)
class Album(val title:String,val year:Int,val artist:Artist)
class AlbumTest extends FunSpec with Matchers {
describe("An Album") {
it("can add an Artist object to the album") {
val album = new Album("Thriller", 1981, new Artist("Michael", "Jackson"))
album.artist.firstName should be("Michael")
}
}
}
-------------------------------------------------------------------
class AlbumTest1 extends FunSuite with Matchers{
test("album test"){
val album = new Album("Thriller",1981,new Artist("Michael","Jackson"))
album.artist.firstName should be ("Michael")
//assert(album.artist.firstName == "michael")
}
}
-------------------------------------------------------------------
class simpleMacher extends FunSpec with Matchers{
describe("test"){
it("listSize"){
val list = 2::4::5::Nil
list.size should be(3)
// assert(list.size==3)
}
}
}
这里有几点需要注意:
1.右边的值需要使用圆括号()括起来
list.size should be 3 // 这种写法会导致编译错误
2.可以将be替换为equal
list.size should equal(3) // 这种写法和 list.size should be(3) 等价
3.在ScalaTest中基本不使用 == 和 != 进行条件断言
如果上面的代码写成:list.size == 5
这样写只会验证list.size == 5这是表达式是true或者false,并不会进行断言的验证
因而不会有TestFailedException异常抛出,测试将继续运行。
3.2 字符串匹配器
String Macher为字符串断言提供了一些有用的方法,利用这些方法可以判断一个字符串是否包含另一个字符串、一个字符串以某个字符串开头或结尾、一个字符串是否能匹配一个正则表达式等。如下面的例子:
val string = """I fell into a burning ring of fire.I went down, down, down and the flames went higher"""
string should startWith("I fell") // 以 "I fell" 字符串开头
string should endWith("higher") // 以 "higher" 字符串结尾
string should not endWith " the end" // 不以 "the end" 字符串结尾
string should include("down, down, down") // 包含 "down, down, down" 字符串
string should not include ("Great balls") // 不包含 "Great balls" 字符串
string should startWith regex ("I.fel+") // 以匹配正则表达式 "I.fel+" 的字符串开头
string should endWith regex ("h.{4}r") // 以匹配正则表达式 "h.{4}r" 的字符串结尾
string should not endWith regex("\\d{5}") // 不以匹配正则表达式 "\\d{5}" 的字符串结尾
string should include regex ("flames?") // 包含匹配正则表达式 "flames?" 的字符串
string should fullyMatch regex ("""I(.|\n|\S)*higher""") // 完全匹配正则表达式 "I(.|\n|\S)*higher"
3.3 关系操作匹配器
ScalaTest框架支持关系运算符,如下面的例子
val answerToLife = 42
answerToLife should be < (50)
answerToLife should not be > (50)
answerToLife should be > (3)
answerToLife should be <= (100)
answerToLife should be >= (0)
answerToLife should be === (42) //===运算符用于检验左边是否等于右边。
answerToLife should not be === (400) //而==只是验证值是否相等并不会验证断言
因此在涉及验证是否相等时最好使用 should be 或 should equal 或 ===。
3.4 误差范围匹配器
浮点数在JVM中实际上是很复杂的,考虑一个算式0.9 - 0.8,在我们看来结果应该是0.1,实际上在REPL中执行这个运算,会得到如下的结果:
scala> 0.9 - 0.8
res0: Double = 0.09999999999999998
显然计算结果是有误差的。在ScalaTest框架中提供一个+-方法来给断言提供一个误差允许范围。如下面的例子:
// 允许右边的范围在 0.1 - 0.01 到 0.1 + 0.01 之间
(0.9 - 0.8) should be (0.1 +- 0.01)
// 允许右边的范围在 40 - 0.3 到 40 + 0.3 之间
(0.4 + 0.1) should not be (40.00 +- 0.30) //在REPL中会输出一个准确值 0.5
3.5 引用匹配器
在Scala中==
运算符不会验证引用是否相等
要验证引用是否相等,在ScalaTest中提供了theSameInstanceAs方法,如下面的例子:
class Artist(val firstName:String,val lastName:String)
val garthBrooks = new Artist("Garth", "Brooks")
val chrisGaines = garthBrooks
garthBrooks should be theSameInstanceAs (chrisGaines)
val debbieHarry = new Artist("Debbie", "Harry")
garthBrooks should not be theSameInstanceAs(debbieHarry)
3.6 Iterable匹配器
对于Scala的可遍历集合类型,ScalaTest
框架提供了多种进行断言的方法。如下面的例子:
List() should be('empty) //'empty,Scala中的符号是不可变的占位符。
8::6::7::5::3::0::9::Nil should contain(7)
3.6 Seq和traversable匹配器
对于Seq
和Traversable
类型的Scala变量,SalaTest
提供了length
和size
这两个Matcher来判定它们的大小(长度)。如下面的例子:
(1 to 9) should have length (9)
(20 to 60 by 2) should have size (21)
实际上根据Scala文档,length和size是等价的,使用哪个完全看你的偏好。
3.7 Map匹配器
而对于Map
类型的Scala变量,ScalaTest
提供了一些特殊的方法,可以用来判断一个key
或者value
是否在Map
中。如下面的例子:
val map = Map("Jimmy Page" -> "Led Zeppelin", "Sting" -> "The Police", "Aimee Mann" -> "Til\' Tuesday")
map should contain key ("Sting") // map中应该包含值为 "Sting" 的key
map should contain value ("Led Zeppelin") // map中应该包含值为 "Led Zeppelin" 的value
map should not contain key("Brian May") // map中应该不包含值为 "Brian May" 的key
3.8 复合操作匹配器
ScalaTest
中的and
和or
方法可以用来在测试中使用组合的断言。如下面的例子:
val hot = List("Anthony Kiedis", "Flea", "Chad Smith", "Josh Klinghoffer")
// hot变量中应该包含 "Anthony Kiedis" 不应该包含 "John Frusciante" 和 "Dave Navarro"
hot should (contain("Anthony Kiedis") and (not contain ("John Frusciante") or contain("Dave Navarro")))
在使用组合的Macher时,圆括号()的使用可能会造成一此困扰,下面是一些规则:
1. and和or的断言必须使用圆括号()包围起来
2. 断言的右边必须使用圆括号()包围起来
以下面的例子来说明上面两条规则:
// 这会导致编译错误
hot should not contain "The Edge" or contain "Kenny G"
// 这也会导致编译错误
hot should not (contain "The Edge" or contain "Kenny G")
// 这是正确的写法
hot should not (contain ("The Edge") or contain ("Kenny G"))
除了上面的两条规则,还有一点需要注意的:使用组合and或or并不是短路的。换句话说,就是所有的子句都会被验证。如下面的例子:
var total = 3
hot should not (contain ("The Edge") or contain {total += 6; "Kenny G"})
total should be (9)
如果发生短路,total should be (9)这里肯定不能通过
not contain ("The Edge")已经是true,则or运算没必要再运行。
但执行完这个测试发现total的值已经是9,说明此时并没有发生短路。
Scala中有一个Option类型,其值可以为Some或None,因此,在Scala中基本不会使用null来做处理。ScalaTest是支持Java的,因此在有些情况下需要用到null。如下面的例子:
gorillaz should (not be (null) and contain ("Damon Albarn"))
如果gorillaz为null则会抛出NullPointerException异常。更好的最法是将组合Matcher拆开:
gorillaz should not be (null)
gorillaz should contain ("Damon Albarn")
经过上面的处理如果gorillaz为null,测试不会通过,但其它的测试不会抛出NullPointerException异常。
3.9 属性匹配器
ScalaTest
也提供了一个很不错的方式来验证对象的属性,如下面的例子:
import scala.collection.mutable.WrappedArray
class Artist(val firstName:String,val lastName:String)
class Album(val title:String,val year:Int,val artist:Artist)
val album = new Album("Blizzard of Ozz", 1980, new Artist("Ozzy", "Osbourne"))
album should have (
'title ("Blizzard of Ozz"),
'year (1980),
'artist (new Artist("Ozzy", "Osbourne")) //写法有误
)
属性Macher可以将对象的属性取出来,然后对这些属性进行断言。这里将属性取出来实际上是使用了对象的getter方法,所以需要保证在对象中有getter方法并且能调用到。
3.10 java.util.Collection匹配器
ScalaTest
是Java友好的,因而它可以像在Scala集合上一样在Java集合上做断言,下面的例子使用了一些在之前用到的方法。ScalaTest
在Java集合上的操作和在Scala集合上的操作是一样的。
import java.util.{List => JList, ArrayList => JArrayList, Map => JMap, HashMap => JHashMap}
val jList: JList[Int] = new JArrayList[Int](20)
jList.add(3); jList.add(6); jList.add(9)
val emptyJList: JList[Int] = new JArrayList[Int]()
emptyJList should be('empty)
jList should have length (3)
jList should have size (3)
jList should contain(6)
jList should not contain (10)
val backupBands: JMap[String, String] = new JHashMap()
backupBands.put("Joan Jett", "Blackhearts")
backupBands.put("Tom Petty", "Heartbreakers")
backupBands should contain key ("Joan Jett")
backupBands should contain value ("Heartbreakers")
backupBands should not contain key("John Lydon")
3.11 Must匹配器
在前面的一些例子中,都是使用Should Macher
的should
这个关键字,实际上可以把前面的should
都换成must
,这完全是等价的。正如之前说过的,Should Macher和Must Macher的不同之处只在测试报告中体现。
val list = 2 :: 4 :: 5 :: Nil
list.size must be(3)
val string = """I fell into a burning ring of fire.
I went down, down, down and the flames went higher"""
string must startWith regex ("I.fel+")
string must endWith regex ("h.{4}r")
val answerToLife = 42
answerToLife must be < (50)
answerToLife must not be >(50)
val garthBrooks = new Artist("Garth", "Brooks")
val chrisGaines = garthBrooks
val debbieHarry = new Artist("Debbie", "Harry")
garthBrooks must be theSameInstanceAs (chrisGaines)
(0.9 - 0.8) must be(0.1 plusOrMinus .01)
List() must be('empty)
1 :: 2 :: 3 :: Nil must contain(3)
(1 to 9).toList must have length (9)
(20 to 60 by 2).toList must have size (21)
val map = Map("Jimmy Page" -> "Led Zeppelin", "Sting" -> "The Police", "Aimee Mann" -> "Til\' Tuesday")
map must contain key ("Sting")
map must contain value ("Led Zeppelin")
map must not contain key("Brian May")
val redHotChiliPeppers = List("Anthony Kiedis", "Flea", "Chad Smith", "Josh Klinghoffer")
redHotChiliPeppers must (contain("Anthony Kiedis") and (not contain ("John Frusciante") or contain("Dave Navarro")))