我有一个代数解释器,我想为它写一个单元测试。
解释器如下:
final case class LiveDbConnector[F[_] : MonadError[*[_], Throwable]](env: Environment[F]) extends DbConnector[F] {
override def read(url: DbUrl, user: DbUser, pw: DbPw): F[DbParams] =
(for {
a <- OptionT(env.get(EnvVariable(url.v)))
b <- OptionT(env.get(EnvVariable(user.v)))
c <- OptionT(env.get(EnvVariable(pw.v)))
} yield DbParams(DbUrl(a.v), DbUser(b.v), DbPw(c.v)))
.value
.flatMap {
case Some(v) => v.pure[F]
case None => DbSettingError.raiseError[F, DbParams]
}
}
代数如下:
trait DbConnector[F[_]] {
def read(url: DbUrl, user: DbUser, pw: DbPw): F[DbParams]
}
具体实现如下:
final case class DbParams(url: DbUrl, user: DbUser, pw: DbPw)
object DbConnector {
def impl[F[_] : MonadError[*[_], Throwable]](env: Environment[F])
: DbConnector[F] =
new LiveDbConnector[F](env)
}
我使用测试框架https://scalameta.org/munit/。
如何为上面的代数编写单元测试?
发布于 2020-06-24 20:49:12
在几乎每个测试框架中,您都可以通过同步调用它来实现这一点
// given
val env: Environment[IO] = ...
val connector: DbConnector[IO] = DbConnector.impl[OP](env)
val url: DbUrl = ...
val user: DbUser = ...
val pw: DbPw = ...
// when
val result = connector.read(url, user, pw).attempt.unsafeRunSync
// then
val expected: DbParams = ...
assert(result == Right(expected))
因为MUnit本身也支持Future,所以你也可以这样做:
// given
val env: Environment[IO] = ...
val connector: DbConnector[IO] = DbConnector.impl[OP](env)
val url: DbUrl = ...
val user: DbUser = ...
val pw: DbPw = ...
// when
connector.read(url, user, pw).attempt.unsafeToFuture.map { result =>
// then
val expected: DbParams = ...
assert(result == Right(expected))
}
有了F
,你就可以灵活地选择最容易测试的实现:cats.effect.IO
,cats.effect.SyncIO
,monix.eval.Task
等等。不同的测试框架只是在你如何在套件中组织你的测试,你可以使用什么类型的匹配器,有时还有可用的集成,但是你可以看到,即使没有集成,你也能够编写测试。
如果代数的每个实现都有仅依赖于输入的输出,并且每个实现都遵循一些约定,那么您可以为其定义规则
class DbConnectorLaws[F[_]: MonadError[*[_], Throwable](
connector: DbConnector[F]
) {
// explicitly expressed contracts that tested class should fulfill
private def expectedReadOuput(dbUrl: DbUrl, user: DbUser, pw: DbPw) = ...
def nameOfReadContract(dbUrl: DbUrl, user: DbUser, pw: DbPw): F[Unit] =
connector.read(dbUrl, user, pw).map { result =>
// Cats laws has some utilities for making it prettier
assert(result == expectedReadOuput(dbUrl, user, pw))
}
}
然后你可以用Scalacheck来测试它
import org.scalacheck.Arbitrary
import org.scalacheck.Prop.forAll
// actual test with laws (cats call them discipline)
trait DbConnectorTests {
val laws: DbConnectorLaws[IO] // simplified, study cats laws if you need it
def readContract()(
implicit
dbUrls: Arbitrary[DbUrl]
users: Arbitrary[DbUser]
pws: Arbitrary[DbPw]
// also other implicits if necessary
) = {
implicit val input = for {
url <- dbUrls
user <- users
pw <- pws
} yield (url, user, pw)
// simplified as well
forall { case (url: DbUrl, user: DbUser, pw: DbPw) =>
laws.nameOfReadContract(url, user, pw).unsafeRunSync // throws if assertion fail
}
}
}
val test = new DbConnectorTests { val laws = new DbConnectorLaws[IO](implementation) }
test.readContract()
然而,你的接口似乎是依赖于实现的,而且是独立的,它不提供任何可以通过这种方式测试的契约。我提到它只是因为在其他问题中,你问了关于“法律”的问题。
https://stackoverflow.com/questions/62491153
复制相似问题