课程涵盖了用Specs 测试,一个Scala 的行为驱动设计(BDD)框架。

扩展规范 extends 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在系统下面的规范 System Under Specification

add是上下文。

add two numberadd three numbers 是例子。

mustEqual 表示期望

1 mustEqual 1 是在你写真正测试之前的一个普通的占位符期望。所有的例子至少要有一个期望。

副本 Duplication

注意两个测试怎样才能都 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
     
    }
  }
}

执行模型 Execution Model

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() /** 用户定义重置函数 **/ }
  "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

你有数据,你想要让它正确,让我们来浏览一下更普遍的匹配用法。(也可见 匹配向导)

mustEqual 一定相等

我们已经见多一些必须匹配的例子。

1 mustEqual 1

"a" mustEqual "a"

参照equality,value equality。

按照顺序的元素 element in a Sequence

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 nust haveKey(k)
map must notHaveKey(k)

map must haveValue(v)
map must notHaveValue(v) 

数字 Number

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)

ThrowA

 must throwA[WhateverException]

这是一个包含body 里面失败的try catch 的一个简写。

你也可以期待一个指定的信息。

a must throwA(WhateverException("message"))

你也可以匹配异常

a must throwA(new Exception) like {
  case Exception(m) => m.startsWith("bad")
}

写下你自己的匹配 Write your own Matchers

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
  }
}

约定是返回一个包含期待值是否为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)
}

也可以看使用Mockito

Spies

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)

在sbt 里运行单个的specs

t-only com.twitter.yourservice.UserSpec

接着会运行这个spec。

> ~ test-only com.twitter.yourservice.UserSpec

会循环运行测试,每个文件修改都会触发测试运行。