Skip to content

Instantly share code, notes, and snippets.

@JonNorman
Last active November 22, 2018 15:41
Show Gist options
  • Save JonNorman/50283b4ce98db58390ec1dd35eeb1ccc to your computer and use it in GitHub Desktop.
Save JonNorman/50283b4ce98db58390ec1dd35eeb1ccc to your computer and use it in GitHub Desktop.
A recap of week 7 scala school: projects, SBT and tests

Scala projects, SBT and Scala Test

Writing Scala isn't much good unless you can run it i.e. run it locally on demand, deploy as a lambda, run as a server etc. etc. So far in scala school we've looked at a lot of the Scala language features and concepts but that isn't that useful if we can't actually run our programs.

So how we can go from just writing Scala source code to actually executing and testing it? For starters, we need to build our source code.

Scala projects and SBT

What does it mean to "build" our source code?

TL;DR: creating executable artefacts from our source code

As with many other languages, Scala is a compiled language. For compiled languages, the source code you write is not executable; it needs to be converted into some other target language ahead of time before the resultant target can be executed. In the case of Scala, it is compiled into Bytecode (which is the same target language that Java compiles to), which can be read and executed by the Java Virtual Machine (JVM). The JVM is a virtual environment that can be spun up on any system; it understands byte-code and translate it to commands that the underlying operating system can understand. This differs to other compiled languages such as C that are compiled directly to assembly code that the underlying operating system can read natively and directly.

So how do we turn our source code into something that can be executed?

TL;DR: we use a build tool

In order to build our Scala source code, we need a build tool and some way of telling it what it should do with our source code i.e. how to build it. For scala, this build tool is called SBT (Scala Build Tool). SBT is a powerful tool - it can be used to compile our code, run the code, run the tests, download dependencies etc. We aren't going to cover all of that here but feel free to have a browse of the docs.

A quick note on project organisation

So far in Scala school we have only been working with single Scala files, but normally you will be dealing with many files in a Scala project and you want your project to be organised in a way such that

  1. you can easily navigate the project structure to find what you need;
  2. other people can do the same; and
  3. you don't have to spend ages telling people or SBT how to build your project.

To satisfy the above conditions, there exist conventions that fellow developers are used to (and that SBT will assume as defaults unless told otherwise). A conventional Scala project has a structure something like this:

- .idea/                    (where IntelliJ stores its local files, caches, preferences, plugins etc. These are usually ignored in git via .gitignore)
- project/                  (contains various configuration files for how SBT itself should run)
    - build.properties          (typically specifies what version of SBT should be run)
    - plugins.sbt               (a list of plugins to be available to SBT)
- src/                      
    - main/ 
        - scala/            (scala source code)
    - test/
        - scala/            (scala test code)
- target/                   (target files generated by SBT)
- build.sbt                 (a description of how to build the project, to be read by SBT)

Note: this is all up for grabs: you can arrange your Scala project however you like, but note that the more you deviate from convention, the more friction there will be to communicate where everything is to other developers and SBT.

So I've got a project structure set up like the above...how do I build this with SBT?

We need three things

  1. source code that compiles;
  2. configuration for SBT to read so that it knows how to build our source code; and
  3. configuration to customise SBT itself.

When SBT is launched in a given directory, the first thing that happens is it will look for a project directory and the contents will affect how SBT starts up e.g. SBT version, SBT plugins etc.

Once SBT is launched, it will seek out a build.sbt file. This contains build definitions - description of your project(s) - that SBT can understand and use.

Scala tests

So far in Scala School we have been writing functions and testing them by calling them below the definition with some sample values. This is not a scalable or dependable way of ensuring that our programs are correct.

Whilst the compiler will warn us of any syntactical errors, it is certainly possible to write a Scala that compiles and runs, but does the wrong thing. Test also show the intent of your code and prevent people from accidentally breaking it and not realising.

The most common way of testing Scala code is to write tests in Scala using the ScalaTest library and to place them in the src/test/scala directory (if you are using a project setup like that outlined above). Once these tests exist, they can be run by running the test command in the SBT console.

Importing ScalaTest

The ScalaTest library is not included in the standard distribution of Scala and so we need to add the library as a dependency on our project. We can do this by adding a line to our build.sbt file like so:

lazy val myProject = (project in file("."))
  .settings(
    name := "My Project",
    libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % Test,
  )

You can usually tell what version you want by going to the documentation and/or by going to Maven, which will even given you the line you need to add to your build definition e.g. - https://mvnrepository.com/artifact/org.scalatest/scalatest_2.12/3.0.5.

Note: Whenever you make a change to your build.sbt, if you already have an SBT shell open, you may need to run the reload command, which instructs the SBT console to go and re-read the build.sbt. IntelliJ may already do this automatically when it detects a change in the build.sbt depending on your IntelliJ settings.

Writing a test

One SBT has downloaded the ScalaTest library, we can write a test that uses it by creating a file in our src/test/scala directory. The name of the test class usually indicates the class or object being tested and ends with Test or Spec (for "Specification"). e.g. if we are testing an object called DataValidator then the test class should be calledDataValidatorTest or DataValidatorSpec.

There are a lot of ways to structure and write tests but a common way of doing so is as follows:

import org.scalatest._
/*
    We create our class and make it extend the scalatest interfaces of FlatSpec and Matchers,
    which allow us to write the "x" should "blah blah blah" statements and express expectations
    via `should`
*/
class ListSpec extends FlatSpec with Matchers { 

  "A List" should "return the first element when calling .head" in {
    val list: List[String] = List("a", "b", "c")
    list.head should be ("a")
  }

  it should "throw NoSuchElementException if the List is empty" in {
    val emptyList: List[Int] = Nil
    a [NoSuchElementException] should be thrownBy {
      emptyList.head
    } 
  }
}

We aren't going to formally cover how and why we should write tests. However over the next few weeks as we write more involved code, bear the following in mind:

  1. Try to make sure your tests test one thing only, that way, when they fail it will be easier to zone in on the area that is broken.

  2. Before you write the body of some function in your main application source code, write a test that expresses what you'd expect with various inputs first. When you run it, these should fail (as it hasn't been written yet). Once all your tests pass, you know you're done. This is formally known as Test Driven Development (TDD).

  3. Have a look at the ScalaTest documentation, there are a lot of useful ways of expressing the expected outcome of a test!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment