-
Book Overview & Buying
-
Table Of Contents
-
Feedback & Rating
Professional Scala
By :
In any program which is bigger than arithmetic operations, programmers should make themselves comfortable when it is possible to ensure that new changes are not breaking old functionalities.
The most common technique for this is unit testing, which is where the programmer tests the functionality of the code in parallel with its development by creating a test code which will verify that the code really satisfies their requirements.
The theme of this section will be introducing tools for unit testing in Scala.
Let's add tests
to our small program. We'll import
<for-students/lesson1/2-project> in our IDE.
This is the directory schema of a Scala project. For adding tests, we should do the following:
build.sbtFor adding dependency, let's add the following line to our
build.sbt:
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.4" % "test"
It's an
expression in Scala DSL (domain-specific language), which means that we should add
scalatest to our set of library
dependencies. Operators
%% and
% are used for forming the name and classifier for published artifacts. You can refer to the
sb
t documentation for more detail:
http://www.scala-sbt.org/1.x/docs/Library-Dependencies.html.
Before compilation,
sbt will download
scalatest from a publicly available repository (Maven central), and when running tests, it will add
scalatest to the classpath.
We will
now run
sbt tests from the command line.
Lesson 1/2-project
courses/pactscala of your home directory, then run the following command:> cd ~/courses/packscala/Lesson 1/2-project
:> sbt test
[info] ExampleSpec: [info] - example test should pass [info] StepTest: [info] - step of unparded word must be interesting
We will now see how to
run
sbt tests from IDEA IDE.
We'll now run sbt Tests from IDEA IDE.

bt test as the configuration.
Now let's look at a simple test:
package com.packt.courseware.l1
import org.scalatest.FunSuite
class ExampleSpec extends FunSuite {
test("example test should pass") {
assert(1==1)
}
}Here, we define a class which is inherited from scalatest FunSuite.
The test expression is called. When the
FunSuite class is initialized and added to a set of tests, the test with
name example test should pass and assert an expression as an argument. For now, this looks like magic, but we will show you how to build such DSLs in the next chapter.
Let's run our test with the help of
sbt:
sbt test
This command will run all tests and evaluate the test expression.
Now, we'll add another test.
src/test/scala/com/packt/courseware/l1/ExampleSpec.scala in 2-projecttrivial test, which asserts the false expression: test("trivial") {
assert(false)
} test("trivial") {
assert(true)
}sbt test again to ensure that all of the tests pass.Remember that,
when writing
chatbot, we want to test one functionality. Our original program only has one function (
main), which contains all of the logic and can't be split into testable parts.
Let's look at Version 2.
Please import
Lesson 1/2-project into your IDE.
package com.packt.courseware.l1
import java.time.LocalTime
import java.time.format.DateTimeFormatter
import scala.io.StdIn
case class LineProcessResult(answer:String,timeToBye:Boolean)
object Chatbot2 {
def main(args: Array[String]): Unit = {
val name = StdIn.readLine("Hi! What is your name? ")
println(s" $name, tell me something interesting, say 'bye' to end the talk")
var c = LineProcessResult("",false)
while(!c.timeToBye){
c = step(StdIn.readLine(">"))
println(c.answer)
}
}
def step(input:String): LineProcessResult = {
input match {
case "bye" => LineProcessResult("ok, bye", true)
case "time" => LineProcessResult(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")),false)
case _ => LineProcessResult("interesting...", false)
}
}
}Here, we see some new constructs:
LineProcessingResult is a case class, where the result of processing one of the lines (that is, the
chatbot answer and quit flag) is stored.
What is the word
case before class?
case classes can
participate in pattern matching (while we call one
case) and are usually used for data objects. We will look at
case classes during the next chapter. It is important to see that an instance of
case classes can be created with the
LineProcessingResult(x,y) syntax (that is, without
new) and an argument to case class constructors (
answers and
timeToBye), which automatically become instance variables of the
case class.
The functionality of processing one line is encapsulated in the
step method, which we can test.
Step receives input from the method argument, not from
System.in, therefore making it easier to test. In the case of directly testing the
main method, we will need to substitute
System.in before
test and return one back after the test is finished.
Ok, let's focus on the first test:
package com.packt.courseware.l1
import org.scalatest.FunSuite
class StepTestSpec extends FunSuite {
test("step of unparded word must be interesting") {
val r = Chatbot2.step("qqqq")
assert(! r.timeToBye)
assert(r.answer == "interesting...")
}
}Writing the second test in the same manner will be an easy task. We will look at this in the following exercise.
Now, let's add the second test, which checks bye.
StepTestSpec class in our project:test("after bye, timeToBye should be set to true")
{
}bye as a parameter:val r = Chatbot2.step("bye")timeToQuit in the returned class is set to true:assert(! r.timeToBye)
test("after bye, timeToBye should be set to true") {
val r = Chatbot2.step("bye")
assert(! r.timeToBye)
sbt test.A more complex task would be to write a test for the time query.
Please note that we can't run the test with the concrete time value, but at least we can be sure that the bot answer can't be parsed back to the time form.
So, what can we do to check the line answer and try to transform it back to time? The solution is provided in the following code:
test("local time must be parser") {
val r = Chatbot2.step("time")
val formatter = DateTimeFormatter.ofPattern("HH:mm:ss")
val t = LocalTime.parse(r.answer,formatter)// assertion is not necessary
}Note that assertion is not necessary. If time does not satisfy the given format, then an exception will be thrown.
It is a good practice to separate functional and effects time for testing. To do this, we will need to substitute the provider of the system time via own.
This will be the first practical task in the next chapter.
Now, let's add the date command to our chatbot program.
date command, which should output the local date in DD:MM:YYYY format: case "date" => LineProcessResult(LocalDate.now().format(DateTimeFormatter.ofPattern("dd:YYYY-MM")),false)test("local date must be parser") {
val r = Chatbot2.step("date")
val formatter = DateTimeFormatter.ofPattern("dd:MM-YYYY")
val t = LocalDate.parse(r.answer,formatter)// assertion is not necessary
}Change the font size
Change margin width
Change background colour