- Professional Scala
- Mads Hartmann Ruslan Shevchenko
- 670字
- 2021-07-23 17:24:25
Running Tests for Chatbot
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.
Note
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.
- Add a second test to the
StepTestSpec
class in our project:test("after bye, timeToBye should be set to true") { }
- In this test:
- Call the step function with
bye
as a parameter:val r = Chatbot2.step("bye")
- Check that after this call that
timeToQuit
in the returned class is set totrue
:assert(! r.timeToBye)
- Call the step function with
- The whole code should be as follows:
test("after bye, timeToBye should be set to true") { val r = Chatbot2.step("bye")
assert(! r.timeToBye) - Run
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.
- Add the following code to the match statement so that it checks for the
date
command, which should output the local date inDD:MM:YYYY
format:case "date" => LineProcessResult(LocalDate.now().format(DateTimeFormatter.ofPattern("dd:YYYY-MM")),false)
- Add a test case for this function.
- The resulting code will be as follows:
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 }