El escenario
En una aplicación que estoy escribiendo actualmente estoy usando gatos de efecto mónada IO en un IOApp .
Si se comienza con un argumento de línea de comandos 'depuración', estoy delegeting mi flujo del programa en un bucle de depuración que espera la entrada del usuario y ejecuta todo tipo de métodos de depuración relevantes. Tan pronto como las prensas de desarrolladores enter
sin ninguna entrada, la aplicación salir del bucle de depuración y salir del método principal, cerrando así la aplicación.
El método principal de esta aplicación se ve más o menos así:
import scala.concurrent.{ExecutionContext, ExecutionContextExecutor}
import cats.effect.{ExitCode, IO, IOApp}
import cats.implicits._
object Main extends IOApp {
val BlockingFileIO: ExecutionContextExecutor = ExecutionContext.fromExecutor(blockingIOCachedThreadPool)
def run(args: List[String]): IO[ExitCode] = for {
_ <- IO { println ("Running with args: " + args.mkString(","))}
debug = args.contains("debug")
// do all kinds of other stuff like initializing a webserver, file IO etc.
// ...
_ <- if(debug) debugLoop else IO.unit
} yield ExitCode.Success
def debugLoop: IO[Unit] = for {
_ <- IO(println("Debug mode: exit application be pressing ENTER."))
_ <- IO.shift(BlockingFileIO) // readLine might block for a long time so we shift to another thread
input <- IO(StdIn.readLine()) // let it run until user presses return
_ <- IO.shift(ExecutionContext.global) // shift back to main thread
_ <- if(input == "b") {
// do some debug relevant stuff
IO(Unit) >> debugLoop
} else {
shutDown()
}
} yield Unit
// shuts down everything
def shutDown(): IO[Unit] = ???
}
Ahora, quiero prueba si, por ejemplo, mi run
método se comporta como lo esperado en mis ScalaTest
s:
import org.scalatest.FlatSpec
class MainSpec extends FlatSpec{
"Main" should "enter the debug loop if args contain 'debug'" in {
val program: IO[ExitCode] = Main.run("debug" :: Nil)
// is there some way I can 'search through the IO monad' and determine if my program contains the statements from the debug loop?
}
}
Mi pregunta
¿Hay algún modo de 'búsqueda / iterar a través de la mónada IO' y determinar si mi programa contiene las declaraciones del bucle de depuración? ¿Tengo que llamar program.unsafeRunSync()
en él para comprobar que?
Se podría aplicar la lógica del run
interior de su propio método, y la prueba de que en lugar, donde no está limitado en el tipo de retorno y hacia adelante run
a su propia implementación. Puesto que run
las fuerzas de su mano para IO[ExitCode]
, no hay mucho que puede expresarse a partir del valor de retorno. En general, no hay manera de "búsqueda" de un IO
valor, ya que sólo un valor que describe un cálculo que tiene un efecto secundario. Si desea inspeccionar su valor subyacente, lo hace mediante la ejecución en el fin del mundo (el main
método), o para sus pruebas, que unsafeRunSync
él.
Por ejemplo:
sealed trait RunResult extends Product with Serializable
case object Run extends RunResult
case object Debug extends RunResult
def run(args: List[String]): IO[ExitCode] = {
run0(args) >> IO.pure(ExitCode.Success)
}
def run0(args: List[String]): IO[RunResult] = {
for {
_ <- IO { println("Running with args: " + args.mkString(",")) }
debug = args.contains("debug")
runResult <- if (debug) debugLoop else IO.pure(Run)
} yield runResult
}
def debugLoop: IO[Debug.type] =
for {
_ <- IO(println("Debug mode: exit application be pressing ENTER."))
_ <- IO.shift(BlockingFileIO) // readLine might block for a long time so we shift to another thread
input <- IO(StdIn.readLine()) // let it run until user presses return
_ <- IO.shift(ExecutionContext.global) // shift back to main thread
_ <- if (input == "b") {
// do some debug relevant stuff
IO(Unit) >> debugLoop
} else {
shutDown()
}
} yield Debug
// shuts down everything
def shutDown(): IO[Unit] = ???
}
Y luego, en su prueba:
import org.scalatest.FlatSpec
class MainSpec extends FlatSpec {
"Main" should "enter the debug loop if args contain 'debug'" in {
val program: IO[RunResult] = Main.run0("debug" :: Nil)
program.unsafeRunSync() match {
case Debug => // do stuff
case Run => // other stuff
}
}
}