使用Specs进行单元测试,specs进行单元测试
使用Specs进行单元测试,specs进行单元测试
这个章节的内容包含使用Specs进行测试,同时介绍了Scala的行为驱动设计(Behavior-Driven Desing BBD)的框架。
- 继承Specification
- 内嵌示例
- 执行模型
- 环境的设置与清理
- doFirst
- doBefore
- doAfter
- 匹配器
- mustEqual
- 包含
- 数量上相同?
- 编写自己的匹配器
- Mock
- Spies
- 在sbt中运行
继承Specification
让我们直接开始吧!
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是“基于指定规范(specification)的系统”
add是一个上下文。
add two numbers 和 add threes numbers都是示例。
mustEqual
表示一个期望(expectation)
在你真正开始写测试之前,1 mustEqual 1
只是你的期望的一个占位符。所有的示例(example)最少要有一个期望(expectation)。
重复
注意在两个测试用例的名称中都有add
。我们可以通过嵌套的期望(expectation)来消除重复的命名。
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
只会在叶子示例(leaf example)上运行。
doFirst 和 doLast
doFirst
/doLast
主要用于一次性的设置。(需要示范吗,不过我不使用它们)
"Foo" should { doFirst { openTheCurtains() } "test stateless methods" in {...} "test other stateless methods" in {...} doLast { closeTheCurtains() } }
匹配器(Matchers)
你有一些数据,并且想要验证它是否正确。我们可以使用matcher。下面我们来看看常用的一些matcher。(参考匹配器指南)
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里的元素
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
这个比使用带有fail的try catch简洁多了。
你也可以加上一个特定消息的判定。
a must throwA(WhateverException("message"))
你也可以对期望进行match匹配:
a must throwA(new Exception) like { case Exception(m) => m.startsWith("bad") }
编写你自己的匹配器
import org.specs.matcher.Matcher
作为一个val
"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 } }
这里对于返回值有一个约定,返回的结果是一个元组,它包含这个期望(expectation)是否为true,以及在不为true的情况下的消息。
作为一个case class
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 可以提高它的共用性。
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
Spies
Spy可以用来对一个具体的对象进行“部分mock”:
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")
不过,使用spy需要一些技巧:
//如果列表为空的话,下面的操作会抛出IndexOutOfBoundsException spiedList.get(0) returns "one"
可以通过doReturn
来处理上面的场景:
doReturn("one").when(spiedList).get(0)
在sbt里单独运行指定的spec
> test-only com.twitter.yourservice.UserSpec
这样会只运行指定的spec。
> ~ test-only com.twitter.yourservice.UserSpec
这样的话,会根据文件的改动来不停地触发这个测试用例的运行。
原文链接: Scala School 翻译: Wld5.com - 朱伟杰译文链接: http://www.wld5.com/4593.html
[ 转载请保留原文出处、译者和译文链接。]
用户点评