В этом уроке вы узнаете о тестировании с помощью Specs, Scala фреймворк для Разработки, основанная на функционировании.

Расширяем Спецификацию

Давайте сразу посмотрим код.

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

Установка, Распаковка

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 Guide»:https://code.google.com/p/specs/wiki/MatchersGuide)

mustEqual

Вы уже видели несколько примеров с mustEqual.

1 mustEqual 1

"a" mustEqual "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 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)

Опции

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

Подобно 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
  }
}

Контракт вернет кортеж, содержащий запись о том верно ожидание или нет, и сообщение когда оно верно или наоборот.

Подобно case классу

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 класс, вы делаете его менее специализированным.

Подделки(Mocks)

import org.specs.Specification
import org.specs.mock.Mockito

abstract 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»:https://code.google.com/p/specs/wiki/UsingMockito

Шпионы(Spies)

Шпионы могут также быть использованы для того, чтобы сделать некоторую «частичную подделку» реальных объектов:

val list = new LinkedList[String]
val spiedList = spy(list)

// методы могут выдать ошибку при использовании шпиона
spiedList.size returns 100

// другие методы также могут быть использованы
spiedList.add("one")
spiedList.add("two")

// и проверка может происходить с помощью шпиона
there was one(spiedList).add("one")

Однако, работа со шпионами может быть сложной:

// если список пуст, то бросается исключение IndexOutOfBoundsException
spiedList.get(0) returns "one"

doReturn должен быть исопльзован в этом случае:

doReturn("one").when(spiedList).get(0)

Запуск индивидальных спеков(тестов) в sbt

> test-only com.twitter.yourservice.UserSpec

Запустится только данный спек.

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

Будет запускаться тест в цикле, с каждой модификацией файла будет срабатывать триггер и будет запускаться тест.