Last active
December 7, 2017 11:10
-
-
Save yasuabe/50bcbfc7dacd8f243f245d7ea5bb5a51 to your computer and use it in GitHub Desktop.
Free Monadを用いた Serviceコードのシナリオテストの試案
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Qiita投稿直前版