September 25, 2014

Executable Specifications

In my previous job, I was given the opportunity to use ScalaTest. It is a library to write and test software specifications. In other words, you can use it to write "executable specifications". Recently, I showed off ScalaTest at my current job. Here is the summary of it.

Suppose we have need table that has the following specification:
Creation:
 - first column must be "Col1"
Addition:
 - add a row (support variable row length)
 - can't add an empty row
...  etc
Retrieval:
 - error if you attempt to get values for a column that doesn't exist
 - empty array if no values for a column
... etc

Here is the ScalaTest code for the specification:

import org.scalatest._

class MyTableSpec extends FlatSpec with Matchers {
 
  behavior of "Addition"
 
  it should "allow you to add a row of values " in {
    val myTable = new MyTable("Col1", "Col2", "Col3")
    myTable.add(1, 100, 100)

    myTable.getColumn("Col1") should be (Array(1))
    myTable.getColumn("Col2") should be (Array(100))
    myTable.getColumn("Col3") should be (Array(100))
  }
 
  it should "allow you to add a row of length less than the number of columns" in {
    val myTable = new MyTable("Col1", "Col2", "Col3")
    myTable.add(1, 100)
  
    myTable.getColumn("Col1") should be (Array(1))
    myTable.getColumn("Col2") should be (Array(100))
  }
 
  it should "throw AssertionError if you attempt to add an empty row" in {
    val myTable = new MyTable("Col1", "Col2", "Col3")
   
    a [AssertionError] should be thrownBy {
      myTable.add()
    }
  }
 
  behavior of "Retrieval"
 
  it should "give you a set of row values for time t" in {
    val myTable = new MyTable("Col1", "Col2", "Col3")
    myTable.add(1, 100, 100)
   
    myTable.getRow(1) should be (Array(1,100,100))
  }
 
  it should "give you an empty array (of type Int) if you retrieve values for a column that doesn't have any" in {
    val myTable = new MyTable("Col1", "Col2", "Col3")
    myTable.add(1, 10)
  
    myTable.getColumn("Col3") should be (Array.empty[Int])
  }
 
  it should "throw NoSuchElementException if you attempt to retrieve values for a column that does not exist" in {
    val myTable = new MyTable("Col1", "Col2", "Col3")
    myTable.add(1, 10, 10)
   
    a [NoSuchElementException] should be thrownBy {
      myTable.getColumn("Col4")
    }
  }
 
  it should "throw AssertionError if you attempt to insert a row with a non-unique value for time " in {
    val myTable = new MyTable("Col1", "Col2", "Col3")
    myTable.add(1, 10, 10)
   
    a [AssertionError] should be thrownBy {
      myTable.add(1, 10, 10)
    }
  }
 
  behavior of "Creation"
 
  it should "throw AssertionError if the first column is not \"Col1\"" in {
    a [AssertionError] should be thrownBy {
      new MyTable("Col1")
    }
  }
 
}

 In addition to unit style testing, you can use ScalaTest to write clear, concise and descriptive tests (which leads to robust software).

At the core of ScalaTest is its Domain Specific Language (DSL) for testing software, and Scala, being flexible and extensible, is a natural fit.

If you are interested in the implementation of this table, contact me.