鍍金池/ 教程/ Scala/ Spec2 內(nèi)置的 Matcher(匹配運算)
Spec2 可選的 Matcher(匹配運算)
測試結(jié)果 Results
Specs2 的設(shè)計思想
Fragments API 簡介
簡介
期望結(jié)果
Spec2 內(nèi)置的 Matcher(匹配運算)

Spec2 內(nèi)置的 Matcher(匹配運算)

前面的兩篇博客
Scala Specs2 測試入門教程(3):測試結(jié)果 ResultScala Specs2 測試入門教程(4):期望結(jié)果簡要介紹了 Spec 的預(yù)期結(jié)果,其中說明

在 Specs2 的 Example對象為一個文字加上任意可以轉(zhuǎn)換成 Result 對象(org.specs2.execute.Result)的對象,它可以是:

  • 一個標(biāo)準測試結(jié)果(Success,failure,pending 等)
  • 一個 Matcher(匹配)結(jié)果
  • 一個布爾值
  • 一一個 ScalaCheck 屬性

標(biāo)準測試結(jié)果

最簡單的 Result 值由 StandardResults Trait 定義,可以有如下幾種值:

  • success:這個測試結(jié)果正常
  • failure:這個測試結(jié)果不滿足預(yù)期
  • anError:測試出現(xiàn)異常
  • skipped:該測試被跳過(不滿足某些條件)
  • pending:通常只這個測試用例還沒實現(xiàn)

此外還有兩種情況:

  • done:這個測試結(jié)果正常顯示「DONE」
  • todo:測試結(jié)果還沒實現(xiàn)顯示」TODO」

其中 Matcher 是用來判斷測試結(jié)果和預(yù)期的結(jié)果是否符合的主要方法.

本篇介紹 Spec2 內(nèi)置的一些 Matcher 類型,它可以分為比較,選項,字元串比較,數(shù)字比較,捕獲異常等種類。

Equalit

比較,該類最常用的 Matcher 為 beEqualTo 來測試是否相等

Matcher 描述
1 must beEqualTo(1) the normal way
1 must be_==(1) with a shorter matcher
1 must_==1 my favorite!
1 mustEqual 1 if you dislike underscores
1 should_==1 for should lovers
1===1 the ultimate shortcut
1 must be equalTo(1) with a literate style
否定形式
1 must not be equalTo(2)
1 must_!=2
1 mustNotEqual 2
1 must be_!=(2)
1!==2

此外還有其它形式的比較:

Matcher 說明
be_=== same as be_== but can be used with some combinators like ^^^ or toSeq because the parameter type is kept
be_==~ checks if (a:A)==(b:A) when there is an implicit conversion from B (the type of b) to A (the type of a)
beTheSameAs checks if a eq b (a must be(b) also works)
beTrue, beFalse shortcuts for Boolean equality
a ==== b similar to a === b but will not typecheck if a and b don’t have the same type

Any

下面的 Matcher 可以應(yīng)用到 Scala 的 Any 類型的對象。

?beLike {case exp => ok }: to check if an object is like a given pattern (ok is a predefined value, ko is the opposite)
?beLike {case exp => exp must beXXX }: to check if an object is like a given pattern, and verifies a condition
?beNull
?beAsNullAs: when 2 objects must be null at the same time if one of them is null
?beOneOf(a, b, c): to check if an object is one of a given list
?haveClass: to check the class of an object
?haveSuperclass: to check if the class of an object as another class as one of its ancestors
?haveInterface: to check if an object is implementing a given interface
?beAssignableFrom: to check if a class is assignable from another
?beAnInstanceOf[T]: to check if an object is an instance of type T
Option/Either

下面的 Matcher 用來檢查 Option 和 Either 類型:

?beSome checks if an element is Some(_)
?beSome(exp) checks if an element is Some(exp)
?beSome(matcher) checks if an element is Some(a) where a satisfies the matcher
?beSome(function: A =>AsResult[B]) checks if an element is Some(a) where function(a) returns a successful Result
(note that a Seq[A] is also a function Int=> A so if you want to check that a sequence is contained in Some you need to use a matcher: beSome(===(Seq(1)))
?beSome.which(function) checks if an element is Some(_) and satisfies a function returning a boolean
?beSome.like(partial function) checks if an element is Some(_) and satisfies a partial function returning a MatchResult
?beNone checks if an element is None
?beAsNoneAs checks if 2 values are equal to None at the same time
?beRight checks if an element is Right(_)
?beRight(exp) checks if an element is `Right(exp)
?beRight(matcher) checks if an element is Right(a) where a satisfies the matcher
?beRight(function: A =>AsResult[B]) checks if an element is Right(a) where function(a) returns a successful Result
(note that a Seq[A] is also a function Int=> A so if you want to check that a sequence is contained in Right you need to use a matcher: beRight(===(Seq(1)))
?beRight.like(partial function) checks if an element is Right(_) and satisfies a partial function returning a MatchResult
?beLeft checks if an element is Left(_)
?beLeft(exp) checks if an element is Left(exp)
?beLeft(matcher) checks if an element is Left(a) where a satisfies the matcher
?beLeft(function: A =>AsResult[B]) checks if an element is Left(a) where function(a) returns a successful Result
(note that a Seq[A] is also a function Int=> A so if you want to check that a sequence is contained in Left you need to use a matcher: beLeft(===(Seq(1)))
?beLeft.like(partial function) checks if an element is Left(_) and satisfies a partial function returning a MatchResult

Try

下面用來測試 Try 類型對象:

?beSuccessfulTry checks if an element is Success(_)
?beSuccessfulTry.withValue(exp) checks if an element is Success(_)
?beSuccessfulTry.withValue(matcher) checks if an element is Success(a) where a satisfies the matcher
?beSuccessfulTry.withValue(function: A =>AsResult[B]) checks if an element is Success(a) where function(a) returns a successful Result
(note that a Seq[A] is also a function Int=> A so if you want to check that a sequence is contained in Success you need to use a matcher: beSuccessfulTry.withValue(===(Seq(1)))
?beSuccessfulTry.which(function) checks if an element is Success(_) and satisfies a function returning a boolean
?beSuccessfulTry.like(partial function) checks if an element is Success(_) and satisfies a partial function returning a MatchResult
?beFailedTry checks if an element is Failure(_)
?beFailedTry.withThrowable[T] checks if an element is Failure(t: T)
?beFailedTry.withThrowable[T](pattern) checks if an element is Failure(t: T) and t.getMessage matches pattern

String

匹配字元串非常常見,下面為字元串匹配:

?beMatching (or be matching) checks if a string matches a regular expression
?=~(s) is a shortcut for beMatching("(.|\\s)*"+s+"(.|\\s)*")
?find(exp).withGroups(a, b, c) checks if some groups are found in a string
?have length checks the length of a string
?have size checks the size of a string (seen as an Iterable[Char])
?be empty checks if a string is empty
?beEqualTo(b).ignoreCase checks if 2 strings are equal regardless of casing
?beEqualTo(b).ignoreSpace checks if 2 strings are equal when you replaceAll("\\s","")
?beEqualTo(b).trimmed checks if 2 strings are equal when trimmed
?beEqualTo(b).ignoreSpace.ignoreCase you can compose them
?contain(b) checks if a string contains another one
?startWith(b) checks if a string starts with another one
?endWith(b) checks if a string ends with another one

Numeric

下面的用來測試數(shù)值:

?beLessThanOrEqualTo compares any Ordered type with <=
1 must be_<=(2)
1 must beLessThanOrEqualTo(2)
?beLessThan compares any Ordered type with <
1 must be_<(2)
1 must beLessThan(2)
?beGreaterThanOrEqualTo compares any Ordered type with >=
2 must be_>=(1)
2 must beGreaterThanOrEqualTo(1)
?beGreaterThan compares any Ordered type with >
2 must be_>(1)
2 must beGreaterThan(1)
?beCloseTo checks if 2 Numerics are close to each other
1.0 must beCloseTo(1,0.5)
4 must be ~(5+/- 2)
?beBetween checks if a value is between 2 others
5 must beBetween(3,6)
5 must beBetween(3,6).excludingEnd
5 must beBetween(4,6).excludingStart
5 must beBetween(4,6).excludingBounds
// with brackets notation
5 must (be[(4,7)])

Exception

下面用來檢查是否拋出異常:

?throwA[ExceptionType] checks if a block of code throws an exception of the given type
?throwA[ExceptionType](message ="boom") additionally checks if the exception message is as expected
?throwA(exception) or throwAn(exception) checks if a block of code throws an exception of the same type, with the
same message
?throwA[ExceptionType].like {case e => e must matchSomething } or
throwA(exception).like {case e => e must matchSomething } allow to verify that the thrown exception satisfies a property
?throwA[ExceptionType](me.like {case e => e must matchSomething } or
throwA(exception).like {case e => e must matchSomething } allow to verify that the thrown exception satisfies a property

Traversable

下面的 Matcher 用來檢查 Traversables 對象類型:

如果你想檢查 Traversables 對象的大?。?/p>

?to check if it is empty
Seq() must be empty
Seq(1,2,3) must not be empty
?to check its size
Seq(1,2) must have size(2)
Seq(1,2) must have length(2) // equivalent to size
?to check its ordering (works with any type T which has an Ordering)
Seq(1,2,3) must beSorted

可以檢查目標(biāo)元素是否在 Traversables 對象中

?if a simple value is contained
Seq(1,2,3) must contain(2)
?if a value matching a specific matcher is contained
Seq(1,2,3) must contain(be_>=(2))
?if a value passing a function returning a Result is contained (MatchResult, ScalaCheck Prop,…)
Seq(1,2,3) must contain((i:Int)=> i must be_>=(2))
?note that a Seq[A] is also a function Int=> A so if you want to check that a sequence is contained in another you need to use a matcher
Seq(Seq(1)) must contain(===(Seq(1)))
?there are also 2 specialized matchers to check the string representation of the elements
Seq(1234,6237) must containMatch("23") // matches with ".*23.*"
Seq(1234,6234) must containPattern(".*234") // matches with !.*234"

檢查符合要求的結(jié)果的次數(shù)

?Seq(1,2,3) must contain(be_>(0)).forall // this will stop after the first failure
?Seq(1,2,3) must contain(be_>(0)).foreach // this will report all failures
?Seq(1,2,3) must contain(be_>(0)).atLeastOnce
?Seq(1,2,3) must contain(be_>(2)).atMostOnce
?Seq(1,2,3) must contain(be_>(2)).exactly(1.times)
?Seq(1,2,3) must contain(be_>(2)).exactly(1)
?Seq(1,2,3) must contain(be_>(1)).between(1.times,2.times)
?Seq(1,2,3) must contain(be_>(1)).between(1,2)

Traversable 元素與其他元素比較(values, matchers, function returning a Result)

?with a set of values
Seq(1,2,3,4) must contain(2,4)
which is the same thing as
Seq(1,2,3,4) must contain(allOf(2,4))
?with a set of matchers
Seq(1,2,3,4) must contain(allOf(be_>(0), be_>(1)))
?checking that the order is satisfied
Seq(1,2,3,4) must contain(allOf(be_>(0), be_>(1)).inOrder)

Note that allOf tries to make each check at least successful once, even if that on the same value. If, on the other hand, you want to specify that each check must succeed on a differentvalue you should use onDistinctValues. For example this will fail:

Seq(1) must contain(allOf(1,1)).onDistinctValues

The eachOf method does the same thing (and this example will fail as well):

Seq(1) must contain(eachOf(1,1))

Another frequent use of Traversable matchers is to check if the Traversable have the right number of elements. For this you can use:

?atLeast, which is actually another name for allOf, where the traversable can contain more elements than required
Seq(1,2,3,4) must contain(atLeast(2,4))
?atMost where the traversable can not contain more elements than required
Seq(2,3) must contain(atMost(2,3,4))
?exactly where the traversable must contain exactly the specified number of elements
Seq(1,2) must contain(exactly(2,1))

The atLeast/atMost/exactly operators work on distinct values by default (because this is easier for counting the correspondance between actual values and expected ones). However you can use onDistinctValues(false) if you don’t care.

Finally, if you want to get the differences between 2 traversables:

Seq(2,4,1) must containTheSameElementsAs(Seq(1,4,2))

Map

下面為檢查 Map 類型的對象:

?haveKey checks if a Map has a given key
Map(1->"1") must haveKey(1)
?haveKeys checks if a Map has several keys
Map(1->"1",2->"2") must haveKeys(1,2)
?haveValue checks if a Map has a given value
Map(1->"1") must haveValue("1")
?haveValues checks if a Map has several values
Map(1->"1",2->"2") must haveValue("1","2")
?havePair checks if a Map has a given pair of values
Map(1->"1") must havePair(1->"1")
?havePairs checks if a Map has some pairs of values
Map(1->"1",2->"2",3->"3") must havePairs(1->"1",2->"2")

但是, Map 也是偏函數(shù),所以:

?beDefinedAt checks if a PartialFunction is defined for a given value
partial must beDefinedAt(1)
?beDefinedBy checks if a PartialFunction is defined for a given value
and returns another one
partial must beDefinedBy(1 -> true)

這些內(nèi)置的 Matcher 具有下面的特點:

  • 這些Matcher的一般形式為 a must matcher
  • 你可以使用 should 代替 must
  • 對于大部分的 Matcher 來說,如果使用 be,have 時,be 和 have 可以省略
  • 你可以在 Matcher 前后使用 not 來反向匹配

比如

def e1 = "Hello world" must be size(11)
def e1 = "Hello world" must have size(11)
def e1 = "Hello world" must  size(11)

是等效的。