19 January 2014

Небольшое введение в Scalatest

IT systems testingScala
Sandbox
Scalatest — это фреймворк для тестирования приложений, поддерживающий разные стили написания тестов и легко интегрирующийся с другими инструментами для JVM, включая IDE и maven.

Каждый из поддерживаемых стилей тестирования в Scalatest создан для использования в определенных целях. Для использования каждого из стилей тестирования, необходимо создать класс, который будет реализовывать trait, в котором определён этот стиль тестирования. Выбранный стиль определяет только то, как выглядят декларации тестов, все остальные возможности фреймворка будут работать одинаково, вне зависимости от того, какой из стилей тестирования был выбран. Авторы рекоммендуют использовать FlatSpec для юнит-тестов и интеграционного тестирования, и FeatureSpec для приемочного тестирования, поэтому пару других стилей я укажу только для примера:

FlatSpec

FlatSpec — хороший шаг вперёд для команд, которые хотят перейти от использования xUnit и других подобных фреимворков к использванию BDD, FlatSpec — это DSL, позволяющий писать тесты в виде как можно более приближённом к написанию спецификации поведения тестируемого класса.
import org.scalatest.FlatSpec

class SetSpec extends FlatSpec {     

  "An empty Set" should "have size 0" in {
    assert(Set.empty.size == 0)
  }

  it should "produce NoSuchElementException when head is invoked" in {
    intercept[NoSuchElementException] {
      Set.empty.head
    }
  }
}


FeatureSpec

FeatureSpec нацелен на создание приемочных тестов, облегчая программистам, работающим с не пограммистами описание приемочных требований.
import org.scalatest._

class TVSet {
  private var on: Boolean = false
  def isOn: Boolean = on
  def pressPowerButton() {
    on = !on
  }
}

class TVSetSpec extends FeatureSpec with GivenWhenThen {

  info("As a TV set owner")
  info("I want to be able to turn the TV on and off")
  info("So I can watch TV when I want")
  info("And save energy when I'm not watching TV")

  feature("TV power button") {
    scenario("User presses power button when TV is off") {

      Given("a TV set that is switched off")
      val tv = new TVSet
      assert(!tv.isOn)

      When("the power button is pressed")
      tv.pressPowerButton()

      Then("the TV should switch on")
      assert(tv.isOn)
    }

    scenario("User presses power button when TV is on") {

      Given("a TV set that is switched on")
      val tv = new TVSet
      tv.pressPowerButton()
      assert(tv.isOn)

      When("the power button is pressed")
      tv.pressPowerButton()

      Then("the TV should switch off")
      assert(!tv.isOn)
    }
  }
}


FunSuite

FunSuite предназначен для команд, которые ранее использовали xUnit, использование этого стиля позволяет комфортно перейти на использование Scalatest и использовать преимущества BDD. Пример использования:
import org.scalatest.FunSuite

class SetSuite extends FunSuite {

  test("An empty Set should have size 0") {
    assert(Set.empty.size == 0)
  }

  test("Invoking head on an empty Set should produce NoSuchElementException") {
    intercept[NoSuchElementException] {
      Set.empty.head
    }
  }
}


FunSpec

FunSpec авторы Scalatest предлагают использовать тем, кто ранее использовал инструменты вроде RSpec для Ruby
import org.scalatest.FunSpec

class SetSpec extends FunSpec {

  describe("A Set") {
    describe("when empty") {
      it("should have size 0") {
        assert(Set.empty.size == 0)
      }

      it("should produce NoSuchElementException when head is invoked") {
        intercept[NoSuchElementException] {
          Set.empty.head
        }
      }
    }
  }
}


WordSpec

Так же, для пользователей specs и specs2 авторы scalatest предлагают использовать трейт WordSpec, который, по их утверждению, является наиболее естественным способом портировать тесты со specs на scalatest.
import org.scalatest.WordSpec

class SetSpec extends WordSpec {

  "A Set" when {
    "empty" should {
      "have size 0" in {
        assert(Set.empty.size == 0)
      }

      "produce NoSuchElementException when head is invoked" in {
        intercept[NoSuchElementException] {
          Set.empty.head
        }
      }
    }
  }
}


Заголовок каждого теста во FlatSpec должен быть предложением, которое описывает тестируемое поведение и состоит из подлежащего, ключевого слова should must или can и конца предложения. после заголовка следует слово in и само тело теста в фигурных скобках. Вот законченный пример теста:

import collection.mutable.Stack
import org.scalatest._

class StackSpec extends FlatSpec {

  "A Stack" should "pop values in last-in-first-out order" in {
    val stack = new Stack[Int]
    stack.push(1)
    stack.push(2)
    assert(stack.pop() == 2)
    assert(stack.pop() == 1)
  }

  it should "throw NoSuchElementException if an empty stack is popped" in {
    val emptyStack = new Stack[String]
    intercept[NoSuchElementException] {
      emptyStack.pop()
    }
  }
}


Assertions

В каждом стиле по умолчанию доступно 3 ассерта:

  • assert для обычных проверок
  • assertResult для проверки совпадения полученного и ожидаемого результата
  • intercept для проверки того, что метод бросает ожидаемое исключение

Scalatest определяет свой метод assert, который скрывает метод, определенный в predef и бросает исключение TestFailedException вместо стандартного AssertionError. Тем не менее, метод assert не позволяет провести различие между ожидаемым и фактическим результатом. Это различие можно провести с помощью метода assertResult:
val a = 5
val b = 2
assertResult(2) {
  a - b
}


в таком случае, если фактический результат не совпадет с ожидаемым, сообщение в исключении TestFailedException будет «Expected 2, but got 3.».
Метод intercept позволяет тестировать ожидаемые исключения:
val s = "hi"
intercept[IndexOutOfBoundsException] {
  s.charAt(-1)
}


поскольку, он возвращает то исключение, которое поймал, его можно так же использовать для проверки сообщений или другой мнформации, добавленной к этому исключению:
intercept[IndexOutOfBoundsException](s charAt -1).getMessage should be == "..."


Matchers

В предыдущем примере можно заметить использование слова be. В scalatest это — тоже одно из ключевых слов, которое можно использовать, если подключить миксин Matchers в класс, который реализует тест. Поскольку, scalatest является очень приближенным к естественному языку DSL, дальнейший код под этим заголовком даётся практически без пояснений.

С помощью этого трейта, можно писать более приближенные к естественному языку спецификации:
Равенство

4 must equal(4)
"foo" must equal("foo")
List("foo", "bar", "baz") must equal(List("foo", "bar", "baz"))
(1, "foo") must equal((1, "foo"))


Размер объекта, длина объекта

"foo" must have length (3)
List(1, 2, 3, 4) must have length (4)
Array('A', 'B') must have length (2)

List(1, 2, 3, 4) must have size (4)
Array('A', 'B') must have size (2)


Проверка строк

"foobarbaz" must startWith("foo")
"foobarbaz" must endWith("baz")
"foobarbaz" must include("bar")

"foobarbaz" must startWith regex ("f[o]+")
"foobarbaz" must endWith regex ("[ba]{2}z")
"foobarbaz" must include regex ("foo[\\w]{3}baz")
"foobarbaz" must fullyMatch regex ("\\w{1}oobarbaz")


Проверка чисел

7 must be < (8)
7 must be > (0)
6 must be <= (7)
7 must be >= (6)
13.43 must equal(13.43)
13.43 must be(13.4 plusOrMinus 0.4)


Проверка булевых свойств

List[Int]() must be('empty) // вызывает list.isEmpty
new File("/tmp") must be('directory) // вызывает file.isDirectory


Коллекции

List(1, 2, 3, 4) must contain(2)
Array("foo", "bar", "baz") must contain("bar")
var users = Map(1 -> "Joe", 2 -> "Lisa", 3 -> "Dr. Evil")
users must contain key (2)
users must contain value ("Dr. Evil")


Свойства класса

case class Recipe(name: String, ingredients: List[String], bakingMinutes: Int)

val cookieRecipe = Recipe("cookieRecipe", List("Flour", "Sugar", "Eggs", "Love"), 30)
cookieRecipe must have(
'name("cookieRecipe"),
'ingredients(List("Flour", "Sugar", "Eggs", "Love")),
'bakingMinutes(30)
)


Соединение проверок логическими функциями

Так же, проверки можно соединять с помощью логических функций and/or, но важно не забыть использовать скобки, так пример users must have size(3) and contain key(3) будет работать неправильно.
users must (have size (3) and contain key (3))
users must (contain value ("Mr. X") or contain value ("Joe"))


В статье не рассмотрено еще множество возможностей этого замечательного фреймворка, таких как использование mock-объектов или использование Selenium, однако даже использование только показанных возможностей позволяет писать красивые и легкие для понимания тесты, которые будут легко читаемы и тесты, максимально приближенные по степени выразительности к спецификации кода.




www.scalatest.org — сайт проекта
Tags:scalascalatesttddbdd
Hubs: IT systems testing Scala
+15
10.1k 49
Leave a comment