Scala Test-Driven Development

2 (1 reviews total)
By Gaurav Sood
  • Instant online access to over 7,500+ books and videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Hello, TDD!

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

Publication date:
October 2016
Publisher
Packt
Pages
198
ISBN
9781786464675

 

Chapter 1. Hello, TDD!

Let's begin this chapter with a brief introduction to Test-Driven Development (or TDD, as it's colloquially known), covering some of its basic concepts. The main goal of this chapter is to give you an appreciation of TDD practices and for you to ascertain what technological niche it fulfills.

This chapter will explore:

  • What is TDD?

  • What is the need for TDD?

  • Changing approach to problem solving

  • Iteratively writing failing tests and fixing them

  • Significance of baby steps

  • Scala

  • Brief introduction to Scala and SBT

  • Setting up the build environment

  • TDD with Scala

  • "Hello World" application

 

What is TDD?


TDD is the practice of writing your tests before writing any application code.

TDD is a practice that is most frequently and strongly advocated by Agile practitioners and theorists. It is one of the major pillars of the Agile methodology. There have been various studies over the years and many white papers published, which clearly state that the use of TDD has resulted in a more successful and robust application code.

TDD consists of the following iterative steps:

This process is also referred to as Red-Green-Refactor-Repeat.

TDD became more prevalent with the use of the Agile software development process, though it can be used as easily with any of the Agile development process's predecessors, such as Waterfall, Iterative, and so on.

At the height of the software revolution in the 1990s, it became evident that the draconian processes and practices developed mostly in the 1980s were slow, officious, likely to fail, and rigid. The Agile development process was created at the Snowbird ski resort in the Wasatch Mountains of Utah. Here, 17 industry thought-leaders met to discuss the problems of the development processes being used with the common goal of creating an adaptive development process that has people at its core rather than requirements. This resulted in the Agile manifesto:

Though TDD is not specifically mentioned in the Agile manifesto (http://agilemanifesto.org), it has become a standard methodology used with Agile. Saying this, you can still use Agile without using TDD.

 

Why TDD?


The need for TDD arises from the fact that there can be constant changes to the application code. This becomes more of a problem when we are using the Agile development process, as it is inherently an iterative development process.

Here are some of the advantages, which underpin the need for TDD:

  • Code quality: Tests on TDD make the programmer more confident of their code. Programmers can be sure of syntactic and semantic correctness of their code.

  • Evolving architecture: A purely test-driven application code gives way to an evolving architecture. This means that we do not have to predefine our architectural boundaries and the design patterns. As the application grows, so does the architecture. This results in an application that is flexible towards future changes.

  • Documenting the code: These tests also document the requirements and application code. Agile purists normally regard comments inside the code as a "smell". According to them your tests should document your code.

  • Avoiding over engineering: Tests that are written before the application code define and document the boundaries. Since all the boundaries are predefined in the tests, it is hard to write application code that breaches these boundaries. This, however, assumes that TDD is being followed religiously.

  • Paradigm shift: When I started with TDD, I noticed that the first question I asked myself after looking at the problem was "How can I solve it?" This, however, is counterproductive. TDD forces the programmer to think about the testability of the solution before its implementation. This would result in solutions, which are testable in nature as we had used tests to derive these solutions. To understand how to test a problem would mean a better understanding of the problem and its edge cases. This in turn can result in the refinement of the requirements or the discovery of some new requirements. Now it has become impossible for me not to think about testability of the problem before the solution. Now the first question I ask myself is; "How can I test it?" This can be done by translating the requirements into tests.

  • Maintainable code: I have always found it easier to work on an application that has historically been test-driven rather than on one that has not. Why? Only because when I make changes to the existing code, the existing tests make sure that I do not break any existing functionality. This results in highly maintainable code, where many programmers can collaborate simultaneously.

  • Refactoring freely: Having a good test coverage over the application code allows the programmer to continuously refactor and improve the code while maintaining an idempotent nature of the application code.

 

Changing our approach to problem solving


Everything we have discussed thus far makes perfect sense in theory, but we need to look at what needs to change in our approach to problem solving if we are to practice the TDD ethos.

I will be more biased towards "me", and will discuss how I made the transition into "TDD land". When I was first asked to build an application using test-driven principles my natural reaction was to doubt every aspect of it. My client at that time had hired a reputable external Agile enablement company to hand-hold developers to learn these new age shenanigans.

My first few questions were "Is it not more time consuming?", "Isn't it QA's job to test?", "Why do we need to test-drive even a very trivial bit of code?", and many more (mostly whiney) questions.

No one gave me a very assuring answer to my doubts, or maybe I was too skeptical to be reassured. So, I pushed myself to endure this and started writing tests first. It was very difficult in the beginning, as I had an obvious solution in my mind and wanted to jump on to writing some application code rather than waste my time with tests. Gradually, it became clearer that the code that was purely test-driven was more robust and had far fewer bugs. Why was that? I realized, when I was doing TDD in its pure form, that I was asking more questions and looking for more failure points for my tests. There were so many assumptions in the requirements that were easy to miss or were deemed too trivial by requirement gatherers. These began showing up when writing tests.

Once you have overcome your initial reluctance and doubts, there comes a stage when TDD becomes a way of thinking. If I am given a problem now, the first thing, which comes to my mind, is its testability.

Let's take a very simple example. Suppose the problem is to write a function to add two numbers. The old me would've bashed a single-line function to return the result of two numbers. No fun in that! Now, I would ask questions like, what is the data type, overflow conditions, exception cases, and many more. Then I would write tests for all these scenarios so they are documented. This is a very trivial example. We will get our hands dirty with another small example by the end of this chapter.

Let's look at what is involved in the process of TDD.

Iteratively writing failing tests

We start with a very minute subset of the problem in hand. In Agile parlance, this problem is also called a story. We write a test that will fail for lack of proper application code to make it work. Then we fix only enough of the application code to make the test pass. At this point, we need to restrain ourselves from over engineering and write just enough code to make the test pass. Then we write more failing tests and fix code to fix these tests. This iterative process goes on till all the requirements of the story have been fully met.

At this point, we realize that if our solution is incomplete then this would mean that the acceptance criteria of the story is incomplete. So instead of assuming what should have been, we ask questions to the customer. This leads to better communication between the customer/analyst and programmers. Historically, programmers have worked in a dark dingy room on a set of requirements that they hardly had any input into. Communication between the programmer and the end customer is very important for the success of any project. More often than not, I have seen customers realize the limitations of their ideas once more questions are asked. They also feel more involved in day-to-day progress rather than just getting to see the final product.

Baby steps

Baby steps are the key to a test-driven approach. It is very hard to constrain oneself from jumping to conclusions or solutions just because we think it is best. TDD dictates only to write enough code to fix the test, even though the code seems rather incomplete or trivial. This prevents us from making any major design decisions. When we get closer to the final solution, we will see a natural design pattern evolving. At this point, we can refactor our code. We will discuss this process later in the book.

 

Brief introduction to Scala and SBT


Let us look at Scala and SBT briefly. This book assumes that the reader is familiar with Scala and therefore will not go into the depth of it.

What is Scala?

Scala is a general purpose programming language. Scala is an acronym for Scalable Language. This reflects the vision of its creators of making Scala a language that grows with the programmer's experience of it. The fact that Scala and Java objects can be freely mixed makes transition from Java to Scala quite easy.

Scala is also a full-blown functional language. Unlike in other languages, such as Haskell, which is a pure functional language, Scala allows interoperability with Java and support for object-oriented programming. Scala also allows the use of both pure and impure functions. Impure functions have side effects such as mutation, I/O, and exceptions. The purist approach to Scala programming encourages the use of pure functions only.

Scala is a type-safe JVM language that incorporates both object-oriented and functional programming in an extremely concise, logical, and extraordinarily powerful language.

Why Scala?

Here are some advantages of using Scala:

  • A functional solution to a problem is always better: This is my personal view and is open to contention. The elimination of mutation from application code allows the application to be run in parallel across hosts and cores without any deadlocks.

  • Better concurrency model: Scala has an Actor model that is better than Java's model of locks on a thread.

  • Concise code: Scala code is more concise than its more verbose cousin, Java.

  • Type safety/static typing: Scala does type checking at compile time.

  • Pattern matching: The case statements in Scala are super powerful.

  • Inheritance: The mixin traits are great, and they definitely reduce code repetition.

  • Domain-specific language (DSL): Scala syntax allows for a programmer to write a natural looking DSL. This ability was carefully built into the original language design. This is a very powerful feature of Scala. Scala test/specs build on top of this feature.

Scala Build Tool

Scala Build Tool (SBT) is a build tool that allows the compiling, running, testing, packaging, and deployment of your code. SBT is mostly used with Scala projects, but it can as easily be used for projects in other languages. In this book, we will be using SBT as a build tool for managing our project and running our tests.

SBT is written in Scala and can use many of the features of the Scala language. Build definitions for SBT are also written in Scala. These definitions are both flexible and powerful. SBT also allows the use of plugins and dependency management. If you have used a build tool such as Maven or Gradle in any of your previous incarnations, you will find SBT a breeze.

Why SBT?

The following are the reasons we choose SBT:

  • Better dependency management:

    • Ivy-based dependency management

    • Only-update-on-request model

  • Can launch REPL in project context

  • Continuous command execution

  • Scala language support for creating tasks

Resources for SBT

Here are a few of the resources for learning SBT:

Setting up a build environment

We have done enough jibber-jabber to lay the groundwork for getting our hands on the driving plates now. Let's start with setting up the environment so we can write, compile, and run a test-driven application using Scala and SBT.

Steps for downloading and installing Scala

  1. Scala can be downloaded from http://www.scala-lang.org/download. Download the latest version available.

    Note

    Scala can also be installed using Homebrew for Mac by typing brew install scala, provided Homebrew is installed. Visit http://brew.sh/ for more information.

  2. After downloading the binaries, unpack the archive. Unpack the archive in a convenient location, such as /usr/local/share/scala on Unix or C:\Program Files\Scala on Windows. You are, however, free to choose a location.

  3. Add the following environment variables:

  4. Test the Scala interpreter (aka the REPL) by typing scala in the Terminal window.

  5. Test the Scala compiler by typing scalac in the Terminal window.

Tip

You can test the version of Scala you are using by typing:

scala -version

This should give an output similar to this:

Scala code runner version 2.11.7 -- Copyright 2002-2013, LAMP/EPFL

Steps for downloading and installing SBT

Tip

Unix: Download the JAR file. Create an executable script file with this content:

#!/bin/bash SBT_OPTS="-Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M" java $SBT_OPTS -jar `dirname $0`/sbt-launch.jar "[email protected]"

Windows: Create a batch file with this content:

set SCRIPT_DIR=%~dp0 java -Xms512M -Xmx1536M -Xss1M -XX:+CMSClassUnloadingEnabled -XX:MaxPermSize=256M -jar "%SCRIPT_DIR%sbt-launch.jar" %*

This is the barebone setup you need to write in order to create a very trivial project that builds using SBT.

Creating a project directory structure

There is a default project directory structure for SBT projects that defines where SBT will look for application code and tests. By default, SBT uses a similar directory structure to Maven:

Build definition files

SBT uses build.sbt as a default build definition file. There are other variations of build definition files, such as .scala and multi-project files. For the scope of this book, we will only look at the build.sbt definition file.

 

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.

 

Summary


TDD is a development methodology where application code is driven by tests. In TDD, a failing test is written first and then is followed by application code to fix the test. There is no doubt that TDD works really well in practice, and it complements the Agile development process. This is evident from many success stories and a lot of research done throughout the industry. This chapter had intended to get rid of any reservations the reader may have had about the use of TDD and to give a quick win example. In the next chapter, we will start on a broader problem and will see how the solution can be reached using TDD.

About the Author

  • Gaurav Sood

    Gaurav Sood is a Scala and XQuery consultant who consults through his own company Omkaar Technologies Limited. He started playing with computers at a very early age and eventually went onto complete his post-graduate degree in computer sciences.

    After working for an Indian software service house for a couple of years, he decided to start his own consultancy business. Since then, his company has provided services to a few Tier 1 Investment banks, the government of the United Kingdom, and publishing houses. He has gained an irrefutable reputation and distinction in the industry. Gaurav has previously worked with HSBC, Deutsche Bank, Reed Elsevier, Lexis Nexis, John Wiley & Sons, HMRC, and Associated News, amongst other smaller names in the industry.

    When he is not consulting or writing, Gaurav can be seen making a fuss of his family. He likes spending time with his beautiful wife and two sons. He also loves volunteering for charity fund raising through sponsored runs. Gaurav also runs a small charitable trust in Shimla, India (New Life), which helps provide education to under-privileged children.

    Browse publications by this author

Latest Reviews

(1 reviews total)
enough said enough said enough said enough said enough said

Recommended For You

Book Title
Access this book, plus 7,500 other titles for FREE
Access now