简体   繁体   中英

Unit testing strategy in Scala with Try, Success, Failure

For the sake of simplicity suppose we have a method called listTail which is defined in the following way:

private def listTail(ls: List[Int]): List[Int] = {
 ls.tail
}

Also we have a method which handles the exception when the list is empty.

private def handleEmptyList(ls: List[Int]): List[Int] = {
 if(ls.isEmpty) List.empty[Int]
}

Now I want to create a safe version of the listTail method, which uses both methods:

import scala.util.{Try, Success, Failure}

def safeListTail(ls: List[Int]): List[Int] = {
 val tryTail: Try[List[Int]] = Try(listTail(ls))
 tryTail match {
   case Success(list) => list
   case Failure(_) => handleEmptyList(ls)
 }
}

My question is, if the two private methods are already tested, then should I test the safe method as well? And if yes, how? I was thinking just to check if the pattern matching cases are executed depending on the input. That is, when we hit the Failure case then the handleEmptyList method is executed. But I am now aware of how to check this.

Or do I need to refactor my code, and put everything in a single method? Even though maybe my private methods are much more complex than this in the example.

My test are written using ScalaTest.

Allowing your methods to throw intentionally is a bad idea and definitely isn't in the spirit of FP. It's probably better to capture failure in the type signature of methods which have the ability to fail.

private def listTail(ls: List[Int]): Try[List[Int]] = Try {
  ls.tail
}

Now your users know that this will return either an Success or a Failure and there's no magic stack unrolling. This already makes it easier to test that method.

You can also get rid of the pattern matching with a simple def safeTailList(ls: List[Int]) = listTail(l).getOrElse(Nil) with this formulation -- pretty nice!

If you want to test this, you can make it package private and test it accordingly.

The better idea would be to reconsider your algorithm. There's machinery that makes getting the safe tail built-in:

def safeTailList(ls: List[Int]) = ls.drop(1)

It is actually the other way around: normally, you don't want to test private methods, only the public ones, because they are the ones that define your interactions with the outside world, as long as they work as promised, who cares what your private methods do, that's just implementation detail.

So, the bottom line is - just test your safeListTail , and that's it, no need to test the inner implementation separately.

BTW, you don't need the match there: Try(listTail(ls)).getOrElse(handleEmptyList(ls)) is equivalent to what you have there ... which is actually not a very good idea, because it swallows other exceptions, not just the one that is thrown when the list is empty, a better approach would be actually to reinstate match but get rid of Try :

  ls match {
     case Nil => handleEmptyList(ls)
     case _ => listTail(ls)
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM