Как написать модульный тест для моей алгебры без тегов? - PullRequest
1 голос
/ 20 июня 2020

У меня есть интерпретатор для алгебры, и я хотел бы написать для него модульный тест.

Интерпретатор выглядит следующим образом:

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/.

Как написать модульный тест для моя вышеуказанная алгебра?

1 Ответ

0 голосов
/ 24 июня 2020

Практически в любой тестовой среде вы можете сделать это, вызвав это синхронно

// 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, et c. И разные тестовые среды различаются только тем, как вы организуете свои тесты в наборах, какие сопоставители вы можете использовать, а иногда и с доступными интеграциями, но вы можете видеть, что можете писать тесты даже без интеграций. реализация, если ваша алгебра имеет вывод, зависящий только от ввода, и который будет следовать некоторым контрактам, вы можете определить l aws для него

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

Однако кажется, что ваш интерфейс зависит от реализации и сам по себе не предоставляет никаких контрактов, которые можно было бы протестировать таким образом. Я упоминаю об этом только потому, что в других вопросах вы спрашивали про «l aws».

...