I'm using ScalaTest for testing which comes in a couple of flavors of which I've chosen the BDD styled FlatSpec style. And I'm really enjoying it. Let's look at some of the things I like about it. First, here are just two simple tests for the constructor of my code under test:
class PortfolioManagerSpec extends FlatSpec with ShouldMatchers {
def withFakeStockPriceFinder(
fileName: String)
(testFunctionBody: (PortfolioManager) => Unit) {
testFunctionBody(new PortfolioManager(fileName)
with FakeStockPriceFinder)
}
"A PortfolioManager" should "be instantiable with a valid file name" in {
withFakeStockPriceFinder("src/configuration/stocks.xml")
{pm =>}
}
it should "throw an exception when given an invalid file name" in {
evaluating {
withFakeStockPriceFinder("fakefilename") {pm =>}
} should produce[FileNotFoundException]
}
...
}
The ScalaTest flavor is chosen simply by extending one of several traits, in this case FlatSpec. FlatSpec gives me the ability to specify test cases in "
Even these simple tests already puts Scalas strong support for functions into play: The withFakeStockPriceFinder method at the top is a curried method that takes first a string argument, and then a function argument, testFunctionBody. The method instantiates the object under test and passes it into the testFunctionBody. Both test cases above use withFakeStockPriceFinder to contruct the object under test, and have it passed into an anonymous function where the actual test code is written. Furthermore the second test makes use of some of the things from the ShouldMatchers trait, namely the evaluating, should and produce methods. Evaluating takes a function argument and executes it, much like my own withFakeStockPriceFinder, but it handles any exceptions thrown and lets me set up expectations for exceptions by calling the should and produce methods in a fluent fashion. -Note that dots and parenthesis in method calls are optional in Scala, as long as there are no ambiguities. This is IMHO is also awesome, and again goes to show how flexible Scala is.
Before showing the rest of my PortfolioManagerSpec I want to touch on another point: TDD is - as we know - a very disciplined way of working, and can be hard to follow all the time but somehow I find that the style of testing promoted by the FlatSpec and ShouldMatchers nudges me towards shorter red-green cycles and a more stringent test-first practice, than I usually have with NUnit. I'm not sure why that is, but it has to do with the way tests are declared with strings rather that method names, I think.
Anyway here's the whole PortfolioManagerSpec:
class PortfolioManagerSpec extends FlatSpec with ShouldMatchers {
def withFakeStockPriceFinder(
fileName: String)
(testFunctionBody: (PortfolioManager) => Unit) {
testFunctionBody(new PortfolioManager(fileName)
with FakeStockPriceFinder)
}
"A PortfolioManager" should "be instantiable with a valid file name" in {
withFakeStockPriceFinder("src/configuration/stocks.xml")
{pm =>}
}
it should "throw an exception when given an invalid file name" in {
evaluating {
withFakeStockPriceFinder("fakefilename") {pm =>}
} should produce[FileNotFoundException]
}
"A PortfolioManager for stocks.xml" should "find 15 symbols and units in the stocks.xml file" in {
withFakeStockPriceFinder("src/configuration/stocks.xml") {
pm =>
val tickersAndUnits = pm.tickersAndUnits
tickersAndUnits.size should be (15)
}
}
it should "find APPL, NSM and XRX ticker symbols" in {
withFakeStockPriceFinder("src/configuration/stocks.xml") {
pm =>
val tickersAndUnits = pm.tickersAndUnits
tickersAndUnits should (contain key ("APPL") and contain key ("NSM") and contain key ("XRX"))
}
}
it should "find 200 ORCL" in {
withFakeStockPriceFinder("src/configuration/stocks.xml") {
pm =>
val tickersAndUnits = pm.tickersAndUnits
tickersAndUnits("ORCL") should be (200)
}
}
it should "produce a asset report and calculate the total net worth" in {
withFakeStockPriceFinder("src/configuration/stocks.xml") {
pm =>
val (totalNetWorth, report) =
pm.generateTotalNetWorthAndSimpleReport
totalNetWorth should be (146250)
print(report)
}
}
}
very nice. thanks.
ReplyDeleteNice one, comes very handly when learning Scala
ReplyDelete