Unidad de pruebas de la mónada IO de gatos-efecto

Florian Baierl:

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 entersin 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 runmétodo se comporta como lo esperado en mis ScalaTests:

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?

Yuval Itzchakov:

Se podría aplicar la lógica del runinterior de su propio método, y la prueba de que en lugar, donde no está limitado en el tipo de retorno y hacia adelante runa su propia implementación. Puesto que runlas 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 IOvalor, 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 mainmé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
    }
  }
}

Supongo que te gusta

Origin http://43.154.161.224:23101/article/api/json?id=190136&siteId=1
Recomendado
Clasificación