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

Creating the App object

We are going to build a simple executable around RetCalc.simulatePlan. It will take a list of parameters separated by spaces, and print the results on the console.

The test we are going to write integrates several components together and will use a full market data set. As such, it is not really a unit test anymore; it is an integration test. For this reason, we suffixed it with IT instead of Spec.

First, copy sp500.tsv and cpi.tsv from https://github.com/PacktPublishing/Scala-Programming-Projects/blob/master/Chapter02/retirement-calculator/src/main/resources/sp500.tsv and https://github.com/PacktPublishing/Scala-Programming-Projects/blob/master/Chapter02/retirement-calculator/src/main/resources/cpi.tsv to src/main/resources, then create a new unit test called SimulatePlanIT in src/test/scala:

package retcalc

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

class SimulatePlanAppIT extends WordSpec with Matchers with TypeCheckedTripleEquals {
"SimulatePlanApp.strMain" should {
"simulate a retirement plan using market returns" in {
val actualResult = SimulatePlanApp.strMain(
Array("1997.09,2017.09", "25", "40", "3000", "2000", "10000"))

val expectedResult =
s"""
|Capital after 25 years of savings: 499923
|Capital after 40 years in retirement: 586435
""".stripMargin
actualResult should === (expectedResult)
}
}
}

We call a function called strMain which will return a string instead of printing it to the console. This way, we can assert on the content printed to the console. To keep things simple, we assume that the arguments are passed in a specific order. We will develop a more user-friendly interface in the next chapter. The arguments are as follows:

  1. A period that we will use in the variables returns, separated by a comma
  2. The number of years of savings
  3. The number of years in retirement
  4. Income
  5. Expenses
  6. Initial capital

The expected value is a string that we define using triple quotes. In Scala, a string enclosed in triple quotes lets you enter special characters such as a quote or newline. It is very convenient to enter multiline strings while keeping a good indentation. The | characters allow you to mark the beginning of each line, and the .stripMargin function removes the white spaces before the |, as well as the | itself in order. In IntelliJ, when you type """ and then hit Enter, it automatically adds the | and .stripMargin after the closing triple quote.

The implementation calls the different functions we implemented earlier. Notice that IntelliJ can autocomplete the name of the files using Ctrl + spacebar, for instance, after EquityData.fromResource("Create a new object SimulatePlanApp in the package retcalc:

package retcalc

object SimulatePlanApp extends App {
println(strMain(args))

def strMain(args: Array[String]): String = {
val (from +: until +: Nil) = args(0).split(",").toList
val nbOfYearsSaving = args(1).toInt
val nbOfYearsInRetirement = args(2).toInt

val allReturns = Returns.fromEquityAndInflationData(
equities = EquityData.fromResource("sp500.tsv"),
inflations = InflationData.fromResource("cpi.tsv"))
val (capitalAtRetirement, capitalAfterDeath) =
RetCalc.simulatePlan(
returns = allReturns.fromUntil(from, until),
params = RetCalcParams(
nbOfMonthsInRetirement = nbOfYearsInRetirement * 12,
netIncome = args(3).toInt,
currentExpenses = args(4).toInt,
initialCapital = args(5).toInt),
nbOfMonthsSavings = nbOfYearsSaving * 12)

s"""
|Capital after $nbOfYearsSaving years of savings:
${capitalAtRetirement.round}
|Capital after $nbOfYearsInRetirement years in retirement:
${capitalAfterDeath.round}
""".stripMargin
}
}

The only code executed when we run our executable will be println(strMain(args)). It is a good practice to keep this code as short as possible because it is not covered by any test. Our function strMain is covered, so we are fairly sure there won't be any unexpected behavior from a single println. args is an Array[String] containing all the arguments passed to the executable.

The first line of strMain uses a pattern matching on List to assign from and until. The variables will be assigned only if the split first argument, in our test "1997.09,2017.09", is a List of two elements.

Then, we load the equity and inflation data from our .tsv files. They contain data from 1900.01 until 2017.09. We then call Returns.fromEquityAndInflationData to compute the real returns.

After having assigned the returns to allReturns, we call simulatePlan with the right arguments. The returns are filtered for a specific period using from and until. Finally, we return String using a string interpolation and triple quotes.

This implementation is the first draft and is quite brittle. It will indeed crash with a horrible ArrayIndexOutOfBoundsException if we do not pass enough parameters to our executable, or with a NumberFormatException if some strings cannot be converted to Int or Double. We will see in the next chapter how we can handle these error cases gracefully, but for now, our calculator does the job as long as we feed it with the right arguments.

You can now run SimulatePlanIT, and it should pass.

Since we built an application, we can also run it as such. Move your cursor to SimulatePlanApp, and hit Ctrl + Shift + R. The app should run and crash with an exception because we did not pass any arguments. Click on the launcher for SimulatePlanApp (underneath the Build menu), then click Edit Configurations. Put the following in program arguments:

1997.09,2017.09 25 40 3000 2000 10000

Then, click OK, and run SimulatePlanApp again. It should print the same content as what we had in our unit test. You can try calling the application with different parameters and observe the resulting calculated capitals.