1

我有一个代数解释器,我想为它编写一个单元测试。

解释器如下:

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/

如何为我的上述代数编写单元测试?

4

1 回答 1

0

在几乎每个测试框架中,您都可以通过同步调用它来做到这一点

// 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()

但是,您的接口似乎依赖于实现并且独立于它自己,它不提供任何可以以这种方式测试的合约。我提到它只是因为您在其他问题中询问了“法律”。

于 2020-06-24T12:49:11.853 回答