这个 Scala 函数是否 heavy/doing 太多东西无法正确进行单元测试?
Is this scala function to heavy/doing too many things to unit test properly?
好的,所以我正在查看测试库,特别是 ScalaTest 和 ScalaMock。
我想写一个测试来测试我写的这个功能:
def gameMenuSelect(): State = {
Try(UI.readOption) match {
case Success(i) => {
i match {
case 1 => HumanGame
case 2 => MachineGame
case 3 => sys.exit(0)
case _ =>
UI.invalidSelectionMsg
ChoosingGame
}
}
case Failure(e) => UI.invalidSelectionMsg; ChoosingGame
}
}
有点背景,UI.readOption
很简单scala.io.StdIn.readInt
。
State
是一个特征 - 随后 HumanGame
、MachineGame
和 ChoosingGame
也是扩展 State
.
的特征
问题是我不知道如何测试它,原因是我觉得这个功能做的太多了。
它正在读取输入,验证给定的输入确实是 number/integer 而不是抛出 NumberFormatException
。鉴于输入是一个整数,它与允许的整数匹配。
我真的觉得有很多东西要测试,还有很多我不确定是否可以单元测试。
请问大家是不是觉得这个函数做的事情太多了,要不要尝试把整型的读取和整型的匹配打散?
谢谢。
在我看来,你的函数有太多的副作用。不仅是读整数,还有sys.exit(0)。您可以更改方法以接受整数作为参数,并添加可用于案例 3 的 EndingGame 状态。然后您将拥有一个易于测试的纯函数。
是的,绝对应该尝试将 "side-effecting" 位、读取和写入与选择逻辑分开。选择逻辑可以return类似于
import scalaz._, Scalaz._
def selectGame(i: Int): GameError \/ State =
i match {
case 1 => HumanGame.right
case 2 => MachineGame.right
case _ => InvalidGame(i).left
}
sealed trait GameError
case class InvalidGame(i: Int) extends GameError
object GameError {
def render(e: GameError): String =
e match {
case InvalidGame(i) =>
s"Invalid game choice: $i. Only 1 and 2 are acceptable values"
}
}
请注意,我还将错误建模为特定类型,而不仅仅是使用字符串。
然后你可以对你的号码解析做同样的事情:
def parseInt(i: String): ParseError \/ Int =
???
对于您的 "effects",您可以使用 Scalaz IO
与控制台进行交互:
def readLine: IO[String] =
IO(StdIn.readLine)
def printLine(line: String): IO[Unit] =
IO(println(line))
然后,使用更多代码,您可以使用 EitherT[IO, E, A]
monad 来 "assemble" 您的所有函数:
// I will provide a full example if you want to go this way
val actions: EitherT[IO, ApplicationError, Unit] =
for {
line <- readLine
i <- parseInt(line)
s <- selectGame(i)
_ <- printLine(s.render)
} yield ()
actions
值将具有 IO
副作用并收集错误,如果有错误则停止进程。
最终,这一切都会让您的测试变得更容易,因为您已经隔离了更容易测试的 "pure" 部分:没有设置,没有模拟,只有纯函数。
好的,所以我正在查看测试库,特别是 ScalaTest 和 ScalaMock。
我想写一个测试来测试我写的这个功能:
def gameMenuSelect(): State = {
Try(UI.readOption) match {
case Success(i) => {
i match {
case 1 => HumanGame
case 2 => MachineGame
case 3 => sys.exit(0)
case _ =>
UI.invalidSelectionMsg
ChoosingGame
}
}
case Failure(e) => UI.invalidSelectionMsg; ChoosingGame
}
}
有点背景,UI.readOption
很简单scala.io.StdIn.readInt
。
State
是一个特征 - 随后 HumanGame
、MachineGame
和 ChoosingGame
也是扩展 State
.
问题是我不知道如何测试它,原因是我觉得这个功能做的太多了。
它正在读取输入,验证给定的输入确实是 number/integer 而不是抛出 NumberFormatException
。鉴于输入是一个整数,它与允许的整数匹配。
我真的觉得有很多东西要测试,还有很多我不确定是否可以单元测试。
请问大家是不是觉得这个函数做的事情太多了,要不要尝试把整型的读取和整型的匹配打散?
谢谢。
在我看来,你的函数有太多的副作用。不仅是读整数,还有sys.exit(0)。您可以更改方法以接受整数作为参数,并添加可用于案例 3 的 EndingGame 状态。然后您将拥有一个易于测试的纯函数。
是的,绝对应该尝试将 "side-effecting" 位、读取和写入与选择逻辑分开。选择逻辑可以return类似于
import scalaz._, Scalaz._
def selectGame(i: Int): GameError \/ State =
i match {
case 1 => HumanGame.right
case 2 => MachineGame.right
case _ => InvalidGame(i).left
}
sealed trait GameError
case class InvalidGame(i: Int) extends GameError
object GameError {
def render(e: GameError): String =
e match {
case InvalidGame(i) =>
s"Invalid game choice: $i. Only 1 and 2 are acceptable values"
}
}
请注意,我还将错误建模为特定类型,而不仅仅是使用字符串。
然后你可以对你的号码解析做同样的事情:
def parseInt(i: String): ParseError \/ Int =
???
对于您的 "effects",您可以使用 Scalaz IO
与控制台进行交互:
def readLine: IO[String] =
IO(StdIn.readLine)
def printLine(line: String): IO[Unit] =
IO(println(line))
然后,使用更多代码,您可以使用 EitherT[IO, E, A]
monad 来 "assemble" 您的所有函数:
// I will provide a full example if you want to go this way
val actions: EitherT[IO, ApplicationError, Unit] =
for {
line <- readLine
i <- parseInt(line)
s <- selectGame(i)
_ <- printLine(s.render)
} yield ()
actions
值将具有 IO
副作用并收集错误,如果有错误则停止进程。
最终,这一切都会让您的测试变得更容易,因为您已经隔离了更容易测试的 "pure" 部分:没有设置,没有模拟,只有纯函数。