课程涵盖了用Specs 测试,一个Scala 的行为驱动设计(BDD)框架。
扩展规范 extends Specification
* 嵌套的例子 nested examples
执行模型 Execution Model
安装和拆解 Setup and TearDown
匹配 Matchers
Mocks 测试
Spies 测试
sbt 里运行 run in sbt
来,直接看代码。
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是在系统下面的规范 System Under Specification
add是上下文。
add two number 和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
}
}
}
"my system" should {
doBefore { resetTheSystem() /** 用户定义重置函数 **/ }
"mess up the system" in {...}
"and again" in {...}
doAfter { cleanThingsUp() }
}
注意 doBefore/doAfter 仅仅在叶例子里运行。
doFirst/doLast 是针对于但设置的。(需要例子,我没用过呀)
"Foo" should {
doFirst { openTheCurtains() }
"test stateless methods" in {...}
"test other stateless methods" in {...}
doLast { closeTheCurtains() }
}
你有数据,你想要让它正确,让我们来浏览一下更普遍的匹配用法。(也可见 匹配向导)
我们已经见多一些必须匹配的例子。
1 mustEqual 1
"a" mustEqual "a"
参照equality,value equality。
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 nust 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)
a must beNone
a must beSome[Type]
a must beSomething
a must beSome(value)
must throwA[WhateverException]
这是一个包含body 里面失败的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
}
}
约定是返回一个包含期待值是否为true以及什么时候不是true的信息的元组。
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))
}
使用case class 让它更可共享。
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)
}
也可以看使用Mockito
Spies 也可以用在一些真实类的“部分mocking”:
val list = new LinkedList[String]
val spiedList = spy(list)
// 方法可以存在spy上
spiedList.size returns 100
// 也可以用其它方法
spiedList.add("one")
spiedList.add("two")
// 在一个spy 上进行验证
there was one(spiedList).add("one")
不管怎样,使用spies 会感到很奇怪。
// 如果list 是空的, 会抛出一个IndexOutOfBoundsException 异常
spiedList.get(0) returns "one"
doReturn 必须使用这个case
doReturn("one").when(spiedList).get(0)
t-only com.twitter.yourservice.UserSpec
接着会运行这个spec。
> ~ test-only com.twitter.yourservice.UserSpec
会循环运行测试,每个文件修改都会触发测试运行。