A small introduction to Scalatest

Scalatest is an application testing framework that supports various test writing styles and integrates seamlessly with other JVM tools, including the IDE and maven.

Each of the supported test styles in Scalatest is designed to be used for specific purposes. To use each of the testing styles, it is necessary to create a class that will implement the trait in which this testing style is defined. The chosen style only determines how the test declarations look, all the other features of the framework will work the same, regardless of which of the test styles was chosen. The authors recommend using FlatSpec for unit tests and integration testing, and FeatureSpec for acceptance testing, so a couple of other styles I will indicate for example only:

Flatsepec

FlatSpec is a good step forward for teams who want to move from using xUnit and other similar frameworks to using BDD, FlatSpec is a DSL that allows you to write tests in the form as close as possible to writing the behavior specification of the tested class.
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 aims to create acceptance tests, making it easier for programmers working with non-programmers to describe acceptance requirements.
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 is intended for teams that previously used xUnit, using this style allows you to comfortably switch to using Scalatest and take advantage of BDD. Usage example:
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
    }
  }
}


Funpec

Scalatest authors suggest using FunSpec for those who have previously used tools like RSpec for 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
        }
      }
    }
  }
}


Wordpec

Also, for users of specs and specs2, scalatest authors suggest using the WordSpec trait, which they claim is the most natural way to port tests from specs to 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
        }
      }
    }
  }
}


The title of each test in FlatSpec should be a sentence that describes the tested behavior and consists of the subject, the keyword should must or can and the end of the sentence. after the heading follows the word in and the body of the test in braces. Here is a complete test example:

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

In each style, 3 assortments are available by default:

  • assert for routine checks
  • assertResult to check if the received and expected result match
  • intercept to verify that the method throws the expected exception

Scalatest defines its assert method, which hides the method defined in predef and throws a TestFailedException instead of the standard AssertionError. However, the assert method does not distinguish between expected and actual results. This distinction can be made using the assertResult method:
val a = 5
val b = 2
assertResult(2) {
  a - b
}


in this case, if the actual result does not match the expected result, the message in the TestFailedException exception will be “Expected 2, but got 3.”.
The intercept method allows you to test expected exceptions:
val s = "hi"
intercept[IndexOutOfBoundsException] {
  s.charAt(-1)
}


since it returns the exception that it caught, it can also be used to check messages or other information added to this exception:
intercept[IndexOutOfBoundsException](s charAt -1).getMessage should be == "..."


Matchers

In the previous example, you can see the use of the word be. In scalatest, this is also one of the keywords that can be used if you connect the Matchers mixin to a class that implements the test. Since scalatest is very close to the natural language of DSL, further code under this heading is given with little or no explanation.

Using this trait, you can write specifications that are closer to the natural language:
Equality

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

Object Size, Object Length

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

Line check

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

Number check

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)

Checking Boolean Properties

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

Collections

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

Class properties

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

Linking Checks with Logical Functions

Also, checks can be connected using the logical functions and / or, but it is important not to forget to use parentheses, as the example users must have size (3) and contain key (3) will not work correctly.
users must (have size (3) and contain key (3))
users must (contain value ("Mr. X") or contain value ("Joe"))


The article has not yet considered the many features of this wonderful framework, such as using mock objects or using Selenium, but even using only the features shown allows you to write beautiful and easy-to-understand tests that will be easy to read and tests that are as close as possible in terms of expressiveness to the specification code.




www.scalatest.org - project site

Also popular now: