Skip to content

Instantly share code, notes, and snippets.

@yasuabe
Last active December 7, 2017 11:10
Show Gist options
  • Save yasuabe/50bcbfc7dacd8f243f245d7ea5bb5a51 to your computer and use it in GitHub Desktop.
Save yasuabe/50bcbfc7dacd8f243f245d7ea5bb5a51 to your computer and use it in GitHub Desktop.
Free Monadを用いた Serviceコードのシナリオテストの試案
import scala.language.higherKinds
import cats.data.StateT
import cats.free.Free
import cats.~>
import cats.instances.all._
import iota.TListK.:::
import iota.{CopK, TNilK}
import Free.inject
// ----------------------------------------
sealed trait ConsoleOp[+A]
case class Ask(prompt: String) extends ConsoleOp[String]
case class Tell(msg: String) extends ConsoleOp[Unit]
sealed trait MqOp[+A]
case class Publish(message: String) extends MqOp[Unit]
type CatsApp[A] = CopK[ConsoleOp ::: MqOp ::: TNilK, A]
// ----------------------------------------
class Console[F[_] <: iota.CopK[_, _]](implicit I: CopK.Inject[ConsoleOp, F]) {
def ask(prompt: String): Free[F, String] = inject[ConsoleOp, F](Ask(prompt))
def tell(msg: String): Free[F, Unit] = inject[ConsoleOp, F](Tell(msg))
}
object Console {
implicit def interacts[F[_] <: iota.CopK[_, _]](implicit I: CopK.Inject[ConsoleOp, F]): Console[F] =
new Console[F]
}
class MQOperations[F[_] <: iota.CopK[_, _]](implicit I: CopK.Inject[MqOp, F]) {
def publish(message: String): Free[F, Unit] = inject[MqOp, F](Publish(message))
}
object MQOperations {
implicit def mqOperation[F[_] <: iota.CopK[_, _]](implicit I: CopK.Inject[MqOp, F]): MQOperations[F] =
new MQOperations[F]
}
// test target ----------------------------------------
object CatsService {
def echoCats[A](implicit I: Console[CatsApp], M: MQOperations[CatsApp])
: Free[CatsApp, Unit] = for {
cat <- I.ask("kitty's name?")
_ <- M.publish(cat)
_ <- I.tell(cat)
} yield ()
}
// test framework ----------------------------------------
import StateT._
type Step = (CatsApp[Any], Any)
type Scenario = List[Step]
type Result[A] = Either[String, A]
type Operations[T] = StateT[Result, Scenario, T]
def validate[A](e: CatsApp[Any])(a: CatsApp[A])(r: Any): Operations[A] = lift(
Either.cond(e == a, r.asInstanceOf[A], s"expected ${e.value}, but got ${a.value}"))
def proceed[A](actual: CatsApp[A]): Scenario => Operations[A] = {
case Nil => lift(Left("no more steps"))
case (expected, result) :: rest => for {
e <- validate(expected)(actual)(result)
_ <- set[Result, Scenario](rest)
} yield e
}
val scenarioInterpreter = new (CatsApp ~> Operations) {
def apply[A](fa: CatsApp[A]) = for {
steps <- get[Result, Scenario]
next <- proceed[A](fa)(steps)
} yield next
}
def scenarioRunner(dsl: Free[CatsApp, Unit])(steps: List[(Product, Any)]) = {
def inject[A[_]](l: A[Any], r: Any)(implicit i: CopK.Inject[A, CatsApp]) =
(CopK.Inject[A, CatsApp].inj(l), r):(CatsApp[Any], Any)
val scenario = steps.map {
case (l, r) => l match {
case l1: ConsoleOp[_] => inject[ConsoleOp](l1, r)
case l2: MqOp[_] => inject[MqOp] (l2, r)
}
}
dsl.foldMap(scenarioInterpreter).run(scenario)
}
// test scenario ----------------------------------------
val echoCatsRunner = scenarioRunner(CatsService.echoCats) _
echoCatsRunner(List(
Ask("kitty's name?") -> "kuro",
Publish("kuro") -> (),
Tell("kuro") -> ()
))
// res0: Result[(Scenario, Unit)] = Right((List(),()))
echoCatsRunner(List(
Ask("kitty's name?") -> "kuro",
Publish("shiro") -> (),
Tell("shiro") -> ()
))
// res1: Result[(Scenario, Unit)] = Left(expected Publish(shiro), but got Publish(kuro))
echoCatsRunner(List(
Ask("kitty's name?") -> "kuro",
Publish("kuro") -> ()
))
// res2: Result[(Scenario, Unit)] = Left(no more steps)
@yasuabe
Copy link
Author

yasuabe commented Dec 7, 2017

Qiita投稿直前版

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment