web/ru/specs.textile (222 lines of code) (raw):
---
prev: coll2.textile
next: concurrency.textile
title: Тестирование с помощью specs
layout: post
---
В этом уроке вы узнаете о тестировании с помощью Specs, Scala фреймворк для Разработки, основанная на функционировании.
* «расширяем Спецификацию»:#example
** вложенные примеры
* «Модель Выполнения»:#scope
* «Установка и Распаковка»:#setup
** doFirst
** doBefore
** doAfter
* «Конструкции сравнения»:#matchers
** mustEqual
** contains
** sameSize?
** пишем свою конструкцию
* «Подделки (Mocks)»:#mocks
* «Шпионы (Spies)»:#spies
* «Запуск в sbt»:#sbt
h2(#example). Расширяем Спецификацию
Давайте сразу посмотрим код.
<pre>
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
}
}
}
</pre>
*Arithmetic* означает, что *System Under Specification*(Система имеет спецификацию)
*add* — это контекст.
*add two numbers* и *add three numbers* — это примеры.
@mustEqual@ отражает наше *ожидание*
@1 mustEqual 1@ является общим заменителем *ожидания*, прежде чем мы начнем писать реальные тесты. Все примеры должны иметь по крайней мере одно ожидание.
h3. Повторяемость
Заметили, что оба теста содержат @add@ в своем имени? Мы избавиться от этого благодаря *вложенным* ожиданиям.
<pre>
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
}
}
}
}
</pre>
h2(#scope). Модель выполнения
<pre>
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
}
}
}
</pre>
h2(#setup). Установка, Распаковка
h3. doBefore и doAfter
<pre>
"my system" should {
doBefore { resetTheSystem() /** определенная пользователем функция сброса */ }
"mess up the system" in {...}
"and again" in {...}
doAfter { cleanThingsUp() }
}
</pre>
*ЗАМЕЧАНИЕ* @doBefore@/@doAfter@ будут работать только с черновыми примерами.
h3. doFirst и doLast
@doFirst@/@doLast@ предназначены во время установки (здесь нужен пример, я не использую их)
<pre>
"Foo" should {
doFirst { openTheCurtains() }
"test stateless methods" in {...}
"test other stateless methods" in {...}
doLast { closeTheCurtains() }
}
</pre>
h2(#matchers). Конструкции сравнения
У вас есть данные, и вы хотите убедиться, что они верные. Давайте рассмотрим наиболее часто используемые конструкции сравнения. (Смотрите также «Matchers Guide»:https://code.google.com/p/specs/wiki/MatchersGuide)
h3. mustEqual
Вы уже видели несколько примеров с mustEqual.
<pre>
1 mustEqual 1
"a" mustEqual "a"
</pre>
Равенство ссылок, равенство значений.
h3. элементы в Sequence
<pre>
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))
</pre>
h3. Записи в Map
<pre>
map must haveKey(k)
map must notHaveKey(k)
map must haveValue(v)
map must notHaveValue(v)
</pre>
h3. Числа
<pre>
a must beGreaterThan(b)
a must beGreaterThanOrEqualTo(b)
a must beLessThan(b)
a must beLessThanOrEqualTo(b)
a must beCloseTo(b, delta)
</pre>
h3. Опции
<pre>
a must beNone
a must beSome[Type]
a must beSomething
a must beSome(value)
</pre>
h3. throwA
<pre>
a must throwA[WhateverException]
</pre>
Запись короче чем try catch с его попыткой выброса исключения в теле блока.
Вы можете выдать специальное сообщение
<pre>
a must throwA(WhateverException("message"))
</pre>
Вы можете также поймать определенное исключение:
<pre>
a must throwA(new Exception) like {
case Exception(m) => m.startsWith("bad")
}
</pre>
h3. Пишем свои условные конструкции
<pre>
import org.specs.matcher.Matcher
</pre>
h4. Подобно val
<pre>
"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
}
}
</pre>
Контракт вернет кортеж, содержащий запись о том верно ожидание или нет, и сообщение когда оно верно или наоборот.
h4. Подобно case классу
<pre>
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))
}
</pre>
Используя case класс, вы делаете его менее специализированным.
h2(#mocks). Подделки(Mocks)
<pre>
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)
}
</pre>
*Смотрите также:* «Использование Mockito»:https://code.google.com/p/specs/wiki/UsingMockito
h2(#spies). Шпионы(Spies)
Шпионы могут также быть использованы для того, чтобы сделать некоторую «частичную подделку» реальных объектов:
<pre>
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")
</pre>
Однако, работа со шпионами может быть сложной:
<pre>
// если список пуст, то бросается исключение IndexOutOfBoundsException
spiedList.get(0) returns "one"
</pre>
@doReturn@ должен быть исопльзован в этом случае:
<pre>
doReturn("one").when(spiedList).get(0)
</pre>
h2(#sbt). Запуск индивидальных спеков(тестов) в sbt
<pre>
> test-only com.twitter.yourservice.UserSpec
</pre>
Запустится только данный спек.
<pre>
> ~ test-only com.twitter.yourservice.UserSpec
</pre>
Будет запускаться тест в цикле, с каждой модификацией файла будет срабатывать триггер и будет запускаться тест.