
Hello World!
Once we have both Scala and SBT set up, it is time to revisit our understanding of TDD. What better way to summarize than to write a very mundane "Hello World" Scala application using TDD. Let us take this application requirement as a user story.
Story definition
As a user of the application
Given that I have a publically accessible no argument function named
displaySalutation
When the function is invoked
Then a string with the value Hello World
is returned.
Note
Notice the language of the story. This is called the Give-When-Then notation, which is used to specify the behavior of the system. Dan North and Chris Matte, as part of (Behavior-Driven Development (BDD), developed this language. We will discuss this parlance in more detail when we look at BDD.
Creating a directory structure
Run these commands on the command line to create the directory structure:
mkdir helloworld
cd helloworld
mkdir -p src/main/scala
mkdir -p src/test/scala
mkdir –p project
Creating a build definition
Create a file using any editor with the content given underneath and save it as build.sbt
:
name := "HelloWorld" version := "1.0" scalaVersion := "2.11.8" libraryDependencies += "org.scalatest" %% "scalatest" % "2.2.6" % "test"
Tip
Use the version of Scala that you got installed locally.
Test first!
How easy would it have been to write just a Scala function that will return Hello World
? Then again, where is the fun in that? Since we have made a commitment to learn TDD, let's start with the test:
- Create a
com.packt
package under thesrc/test/scala
folder. - Write your first test as follows and save it as
HelloTest.scala
:package com.packt import org.scalatest.FunSuite class HelloTests extends FunSuite { test("displaySalutation returns 'Hello World'") { assert(Hello.displaySalutation == "Hello World") } }
- Now, on the command line run
sbt test
from under the project root directory:/Packt/
. - You will see a screen output similar to this:
$ sbt test [info] Loading project definition from /helloworld/project [info] Set current project to Chap1 (in build file:/Packt/ helloworld /) [info] Compiling 1 Scala source to /Packt/ helloworld /target/scala/classes... [info] Compiling 1 Scala source to /Packt/ helloworld /target/scala/test- classes... [error] /Packt/ helloworld /src/test/scala/com/packt/ HelloTest.scala:7: not found: value Hello [error] assert(Hello.displaySalutation == "Hello World") [error] ^ [error] one error found [error] (test:compileIncremental) Compilation failed
Hey presto! An error. Well that's what we had expected. Congratulations! This is your first failing test. Now let's fix the test one step at a time.
Looking at the compilation error, we can see that the compiler could not find the Hello.scala
class. Let's create a new package com.packt
under src/main/scala
. Add a class Hello.scala
here with this content:
package com.packt object Hello { }
You may be wondering why we are adding an empty class here. This is because we are doing TDD and just doing enough to make the first error go away. Now we will re-run sbt test
and see output similar to this:
[info] Loading project definition from /Packt/HelloWorld/project [info] Set current project to Chap1 (in build file:/Packt/HelloWorld/) [info] Compiling 1 Scala source to /Packt/HelloWorld/target/ scala/classes... [info] Compiling 1 Scala source to /Packt/HelloWorld/target/scala/test- classes... [error] /Packt/Chap1/src/test/scala-2.11/com/packt/HelloTest.scala:7: value displaySalutation is not a member of object com.packt.Hello [error] assert(Hello.displaySalutation == "Hello World") [error] ^ [error] one error found [error] (test:compileIncremental) Compilation failed
Again we get an error, but this time it is complaining about a missing member, displaySalutation
. At this point, we can make changes to the class Hello
and introduce a member function, displaySalutation
. It will look like this:
package com.packt object Hello { def displaySalutation = "" }
Now re-run sbt test
. This time the output should look similar to this:
$ sbt test [info] Loading project definition from /Packt/HelloWorld/project [info] Set current project to Chap1 (in build file:/Packt/ HelloWorld /) [info] Compiling 1 Scala source to /Packt/ HelloWorld /target/scala/classes... [info] HelloTests: [info] - displaySalutation returns 'Hello World' *** FAILED *** [info] "[]" did not equal "[Hello World]" (HelloTest.scala:7) [info] Run completed in 227 milliseconds. [info] Total number of tests run: 1 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 0, failed 1, canceled 0, ignored 0, pending 0 [info] *** 1 TEST FAILED *** [error] Failed tests: [error] com.packt.HelloTests [error] (test:test) sbt.TestsFailedException: Tests unsuccessful
The output is better this time as there are no compilation problems. The build fails because of the failure of the test. Our test makes an assertion that the output of displaySalutation
should be Hello World
, and our current implementation of Hello.scala
(deliberately) returns an empty string. At this point, we can change the empty string to "Hello World"
so the content of Hello.scala
looks like this:
package com.packt object Hello { def displaySalutation = "Hello World" }
Let us re-run the task sbt test
. This time we will get the expected output:
$ sbt test [info] Loading project definition from /Packt/HelloWorld/project [info] Updating {file:/Packt/HelloWorld/project/}helloworld-build... [info] Resolving org.fusesource.jansi#jansi;1.4 ... [info] Done updating. [info] Set current project to HelloWorld (in build file:/Packt/HelloWorld/) [info] Updating {file:/Packt/HelloWorld/}helloworld... [info] Resolving jline#jline;2.12.1 ... [info] Done updating. [info] Compiling 1 Scala source to /Packt/HelloWorld/target/scala/classes... [info] Compiling 1 Scala source to /Packt/HelloWorld/target/scala/test- classes... [info] HelloTests: [info] - the name is set correctly in constructor [info] Run completed in 327 milliseconds. [info] Total number of tests run: 1 [info] Suites: completed 1, aborted 0 [info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0 [info] All tests passed.
This leads us to a solution that is fully test-driven. We can argue about the notion of too much testing and the triviality of the tests, but let's hold off till later chapters. For now, just bask in the glory of having written your first Scala TDD application.