本節課將介紹如何使用specs —— 一個Scala行為驅動設計(BDD)框架,來進行測試。
[TOC=2,2]
## 擴展規格
讓我們直接開始。
~~~
import org.specs._
object ArithmeticSpec extends Specification {
"Arithmetic" should {
"add two numbers" in {
1 + 1 mustEqual 2
}
"add three numbers" in {
1 + 1 + 1 mustEqual 3
}
}
}
~~~
**Arithmetic(算術)**?是一個?**規范約束下的系統**
**add(加)**?是一個上下文。
**add two numbers(兩個數相加)**,和?**add three numbers(三個數字相加)**?是例子。
`mustEqual`?表示?**預期**
`1 mustEqual 1`?是編寫實際測試前使用的一種常見的?**預期**?占位符。所有的測試用例都應該至少有一個預期。
### 復制
注意到兩個測試都是怎樣將?`add`?加在其名稱中的嗎?我們可以通過?**嵌套**?預期擺脫這種重復。
~~~
import org.specs._
object ArithmeticSpec extends Specification {
"Arithmetic" should {
"add" in {
"two numbers" in {
1 + 1 mustEqual 2
}
"three numbers" in {
1 + 1 + 1 mustEqual 3
}
}
}
}
~~~
## 執行模型
~~~
object ExecSpec extends Specification {
"Mutations are isolated" should {
var x = 0
"x equals 1 if we set it." in {
x = 1
x mustEqual 1
}
"x is the default value if we don't change it" in {
x mustEqual 0
}
}
}
~~~
## Setup, Teardown
### doBefore & doAfter
~~~
"my system" should {
doBefore { resetTheSystem() /** user-defined reset function */ }
"mess up the system" in {...}
"and again" in {...}
doAfter { cleanThingsUp() }
}
~~~
**注意**?`doBefore`/`doAfter`?只能運行在葉子用例上。
### doFirst & doLast
`doFirst`/`doLast`?用來做一次性的設置。(需要例子,我不使用這個)
~~~
"Foo" should {
doFirst { openTheCurtains() }
"test stateless methods" in {...}
"test other stateless methods" in {...}
doLast { closeTheCurtains() }
}
~~~
## Matchers
你有數據,并且想要確保它是正確的。讓我們看看最常用的匹配器是如何幫助你的。 (參考?[匹配器指南](http://code.google.com/p/specs/wiki/MatchersGuide)?)
### mustEqual
我們已經看到幾個mustEqual的例子了。
~~~
1 mustEqual 1
"a" mustEqual "a"
~~~
引用相等,值相等。
### 序列中的元素
~~~
val numbers = List(1, 2, 3)
numbers must contain(1)
numbers must not contain(4)
numbers must containAll(List(1, 2, 3))
numbers must containInOrder(List(1, 2, 3))
List(1, List(2, 3, List(4)), 5) must haveTheSameElementsAs(List(5, List(List(4), 2, 3), 1))
~~~
### 映射中的元素
~~~
map must haveKey(k)
map must notHaveKey(k)
map must haveValue(v)
map must notHaveValue(v)
~~~
### 數字
~~~
a must beGreaterThan(b)
a must beGreaterThanOrEqualTo(b)
a must beLessThan(b)
a must beLessThanOrEqualTo(b)
a must beCloseTo(b, delta)
~~~
### Options
~~~
a must beNone
a must beSome[Type]
a must beSomething
a must beSome(value)
~~~
### throwA
~~~
a must throwA[WhateverException]
~~~
這是一個針對try\catch塊中有異常拋出的用例的簡寫。
您也可以期望一個特定的消息
~~~
a must throwA(WhateverException("message"))
~~~
您也可以匹配異常:
~~~
a must throwA(new Exception) like {
case Exception(m) => m.startsWith("bad")
}
~~~
### 編寫你自己的匹配器
~~~
import org.specs.matcher.Matcher
~~~
#### 作為一個不變量
~~~
"A matcher" should {
"be created as a val" in {
val beEven = new Matcher[Int] {
def apply(n: => Int) = {
(n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
}
}
2 must beEven
}
}
~~~
契約是返回一個包含三個值的元組,分別是期望是否為真、為真時的消息和為假時的消息。
#### 作為一個樣本類
~~~
case class beEven(b: Int) extends Matcher[Int]() {
def apply(n: => Int) = (n % 2 == 0, "%d is even".format(n), "%d is odd".format(n))
}
~~~
使用樣本類可以增加代碼的重用性。
## Mocks
~~~
import org.specs.Specification
import org.specs.mock.Mockito
class Foo[T] {
def get(i: Int): T
}
object MockExampleSpec extends Specification with Mockito {
val m = mock[Foo[String]]
m.get(0) returns "one"
m.get(0)
there was one(m).get(0)
there was no(m).get(1)
}
~~~
**參考**?[Using Mockito](http://code.google.com/p/specs/wiki/UsingMockito)
## Spies
Spies(間諜)可以對真正的對象做一些“局部mocking”:
~~~
val list = new LinkedList[String]
val spiedList = spy(list)
// methods can be stubbed on a spy
spiedList.size returns 100
// other methods can also be used
spiedList.add("one")
spiedList.add("two")
// and verification can happen on a spy
there was one(spiedList).add("one")
~~~
然而,使用間諜可能會出現非常詭異的情況:
~~~
// if the list is empty, this will throws an IndexOutOfBoundsException
spiedList.get(0) returns "one"
~~~
這里必須使用?`doReturn`?:
~~~
doReturn("one").when(spiedList).get(0)
~~~
## 在sbt中運行單個specs
~~~
> test-only com.twitter.yourservice.UserSpec
~~~
將只運行那個規范。
~~~
> ~ test-only com.twitter.yourservice.UserSpec
~~~
將在一個循環中運行該測試,文件的每一次修改都將觸發測試運行。
Built at?[@twitter](http://twitter.com/twitter)?by?[@stevej](http://twitter.com/stevej),?[@marius](http://twitter.com/marius), and?[@lahosken](http://twitter.com/lahosken)?with much help from?[@evanm](http://twitter.com/evanm),?[@sprsquish](http://twitter.com/sprsquish),?[@kevino](http://twitter.com/kevino),?[@zuercher](http://twitter.com/zuercher),?[@timtrueman](http://twitter.com/timtrueman),?[@wickman](http://twitter.com/wickman), and[@mccv](http://twitter.com/mccv); Russian translation by?[appigram](https://github.com/appigram); Chinese simple translation by?[jasonqu](https://github.com/jasonqu); Korean translation by?[enshahar](https://github.com/enshahar);
Licensed under the?[Apache License v2.0](http://www.apache.org/licenses/LICENSE-2.0).