Scala Programming Projects
上QQ阅读APP看书,第一时间看更新

Writing a unit test for the accumulation phase

We want a function that behaves similarly to the FV function in Excel: it calculates the future value of an investment based on a constant interest rate. As we follow a TDD approach, the first thing to do is create a failing test:

  1. Create a new Scala project called retirement_calculator. Follow the same instructions as in Chapter 1Writing Your First Program.
  1. Right-click on the directory src/main/scala and select New | Package. Name it retcalc.
  2. Right-click on the new package and select New | Scala class. Name it RetCalcSpec.
  3. Enter the following code:
package retcalc

import org.scalactic.{Equality, TolerantNumerics, TypeCheckedTripleEquals}
import org.scalatest.{Matchers, WordSpec}

class RetCalcSpec extends WordSpec with Matchers with TypeCheckedTripleEquals {

implicit val doubleEquality: Equality[Double] =
TolerantNumerics.tolerantDoubleEquality(0.0001)

"RetCalc.futureCapital" should {
"calculate the amount of savings I will have in n months" in {
val actual = RetCalc.futureCapital(
interestRate = 0.04 / 12, nbOfMonths = 25 * 12,
netIncome = 3000, currentExpenses = 2000,
initialCapital = 10000)
val expected = 541267.1990
actual should ===(expected)
}
}

As seen in Chapter 1Writing Your First Program, in the section Creating my first project, we used the WordSpec ScalaTest style. We also used a handy feature called TypeCheckedTripleEquals. It provides a powerful assertion, should ===, that ensures at compile time that both sides of the equality have the same type. The default ScalaTest assertion should verifies the type equality at runtime. We encourage you to always use should ===, as it will save a lot of time when refactoring code.

Besides, it lets us use a certain amount of tolerance when comparing double values. Consider the following declaration:

implicit val doubleEquality: Equality[Double] =
TolerantNumerics.tolerantDoubleEquality(0.0001)

It will let a double1 should === (double2) assertion pass if the absolute difference between double1 and double2 is lower than 0.0001. This allows us to specify expected values to only the fourth digit after the decimal point. It also avoids hitting floating point calculation issues. For instance, enter the following code in the Scala Console:

scala> val double1 = 0.01 -0.001 + 0.001
double1: Double = 0.010000000000000002

scala> double1 == 0.01
res2: Boolean = false

It can be a bit surprising, but this is a well-known problem in any language that encodes floating point numbers in binary. We could have used BigDecimal instead of Double to avoid this kind of issue, but for our purposes, we do not need the additional precision offered by BigDecimal, and BigDecimal computations are much slower.

The body of the test is quite straightforward; we call a function and expect a value. As we wrote the test first, we had to work out what would be the expected result before writing the production code. For non-trivial calculations, I generally use Excel or LibreOffice. For this function, the expected value can be obtained by using the formula =-FV(0.04/12,25*12,1000,10000,0). We assume that the user saves the full difference between his or her income and his/her expenses every month. Hence, the PMT parameter in the FV function is 1,000 = netIncome - currentExpenses.

We now have a failing test, but it does not compile, as the RetCalc object and its futureCapital function do not exist yet. Create a RetCalc object in a new package retcalc in src/main/scala, then select the red futureCapital call in the unit test and hit Alt + Enter to generate the function body. Fill in the names and types of the parameters. You should end up with the following code in RetCalc.scala:

package retcalc

object RetCalc {
def futureCapital(interestRate: Double, nbOfMonths: Int, netIncome:
Int, currentExpenses: Int, initialCapital: Double): Double = ???
}

Open RetCalcSpec, and type Ctrl + Shift + R to compile and run it. Everything should compile and the test should fail.