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:
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.
FeatureSpec aims to create acceptance tests, making it easier for programmers working with non-programmers to describe acceptance requirements.
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:
Scalatest authors suggest using FunSpec for those who have previously used tools like RSpec for Ruby
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.
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:
In each style, 3 assortments are available by default:
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:
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:
since it returns the exception that it caught, it can also be used to check messages or other information added to this exception:
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:
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.
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
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