Book Image

Scala Test-Driven Development

By : Gaurav Sood
Book Image

Scala Test-Driven Development

By: Gaurav Sood

Overview of this book

Test-driven development (TDD) produces high-quality applications in less time than is possible with traditional methods. Due to the systematic nature of TDD, the application is tested in individual units as well as cumulatively, right from the design stage, to ensure optimum performance and reduced debugging costs. This step-by-step guide shows you how to use the principles of TDD and built-in Scala testing modules to write clean and fully tested Scala code and give your workflow the change it needs to let you create better applications than ever before. After an introduction to TDD, you will learn the basics of ScalaTest, one of the most flexible and most popular testing tools around for Scala, by building your first fully test-driven application. Building on from that you will learn about the ScalaTest API and how to refactor code to produce high-quality applications. We’ll teach you the concepts of BDD (Behavior-driven development) and you’ll see how to add functional tests to the existing suite of tests. You’ll be introduced to the concepts of Mocks and Stubs and will learn to increase test coverage using properties. With a concluding chapter on miscellaneous tools, this book will enable you to write better quality code that is easily maintainable and watch your apps change for the better.
Table of Contents (16 chapters)
Scala Test-Driven Development
Credits
About the Author
Acknowledgments
About the Reviewer
www.PacktPub.com
Preface

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:

  1. mkdir helloworld

  2. cd helloworld

  3. mkdir -p src/main/scala

  4. mkdir -p src/test/scala

  5. 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:

  1. Create a  com.packt package under the src/test/scala folder.

  2. 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") 
            } 
          } 
    
  3. Now, on the command line run sbt test from under the project root directory :/Packt/.

  4. 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.