如何在ZIO测试中正确验证计划的调用

不可抗拒

我是ZIO和ZIO测试的新手,我想测试我在ZIO v1.0.0RC17下编写的调度服务:

服务:

import zio.{RIO, Schedule}
import zio.clock.Clock
import zio.duration._

trait ModuleA {
  def moduleA: ModuleA.Service
}

object ModuleA {
  trait Service {
    def schedule(arg: Int): RIO[Clock, Unit]
  }
}

trait ModuleALive extends ModuleA {

  def moduleB: ModuleB.Service

  override def moduleA: ModuleA.Service = new ModuleA.Service {
    override def schedule(arg: Int): RIO[Clock, Unit] = {
      moduleB.run(arg).repeat(Schedule.spaced(1 day)).map(_ => ())
    }
  }
}

trait ModuleB {
  def moduleB: ModuleB.Service
}

object ModuleB {
  trait Service {
    def run(arg: Int): RIO[Clock, Unit]
  }
}

基本而言,ModuleA的服务应该每天运行一次ModuleB的Service方法,并将参数输入到ModuleA.Service.run中。

我想写的测试:

import java.util.concurrent.atomic.AtomicInteger

import zio.clock.Clock
import zio.duration._
import zio.test.environment.TestClock
import zio.test.{DefaultRunnableSpec, assertCompletes, suite, testM}
import zio.{RIO, Task, ZIO}

object ExampleSpec extends DefaultRunnableSpec(ExampleSuite.suite1)

object ExampleSuite {

  val counter: AtomicInteger = new AtomicInteger(0)

  trait ModuleBTest extends ModuleB {
    override def moduleB: ModuleB.Service = new ModuleB.Service {
      override def run(arg: Int): RIO[Clock, Unit] = ZIO.effectTotal(counter.incrementAndGet())
    }
  }

  object ModuleATest extends ModuleALive with ModuleBTest

  def verifyExpectedInvocationCount(expectedInvocationCount: Int): Task[Unit] = {
    val actualInvocations = counter.get()
    if (counter.get() == expectedInvocationCount)
      ZIO.succeed(())
    else
      throw new Exception(s"expected invocation count: $expectedInvocationCount but was $actualInvocations")
  }

  val suite1 = suite("a")(
    testM("a should correctly schedule b") {
      for {
        _ <- ModuleATest.moduleA.schedule(42).fork
        _ <- TestClock.adjust(12 hours)
        _ <- verifyExpectedInvocationCount(1)
        _ <- TestClock.adjust(12 hours)
        _ <- verifyExpectedInvocationCount(2)
      } yield assertCompletes
    }
  )
}

我使用计数器简化了测试,实际上我想使用Mockito来验证调用计数以及正确的参数。但是,此测试不起作用。在我的理解中,这是由于https://zio.dev/docs/howto/howto_test_effects#testing-clock中所述的计时开销引入了竞争条件

现在,有一些示例,说明了如何使用Promise解决此问题。我尝试通过将计数器替换为这样的承诺来做到这一点:

import java.util.concurrent.atomic.AtomicInteger

import zio.test.{DefaultRunnableSpec, assertCompletes, suite, testM}
import zio.{Promise, Task, UIO, ZIO}

object ExampleSpec extends DefaultRunnableSpec(ExampleSuite.suite1)

object ExampleSuite {

  val counter: AtomicInteger = new AtomicInteger(0)
  var promise: UIO[Promise[Unit, Int]] = Promise.make[Unit, Int]

  trait ModuleBTest extends ModuleB {
    override def moduleB: ModuleB.Service = new ModuleB.Service {
      override def run(arg: Int) = promise.map(_.succeed(counter.incrementAndGet))
    }
  }

  object ModuleATest extends ModuleALive with ModuleBTest

  def verifyExpectedInvocationCount(expectedInvocationCount: Int, actualInvocations: Int): Task[Unit] = {
    if (actualInvocations == expectedInvocationCount)
      ZIO.succeed(())
    else
      throw new Exception(s"expected invocation count: $expectedInvocationCount but was $actualInvocations")
  }

  val suite1 = suite("a")(
    testM("a should correctly schedule b") {
      for {
        _ <- ModuleATest.moduleA.schedule(42).fork
        p <- promise
        actualInvocationCount <- p.await
        _ <- verifyExpectedInvocationCount(expectedInvocationCount = 1, actualInvocationCount)
      } yield assertCompletes
    }
  )
}

使用此功能,测试不会终止。但是,我很确定我错误地使用了诺言。

一个人如何正确地应对这种测试情况?

亚当·弗雷泽

在您的示例中,类型promise是,UIO[Promise[Unit, Int]]因此您每次都创建一个新的Promise。结果,您完成的承诺与测试正在等待的承诺有所不同,从而导致无法终止。

要对此进行测试,您可以执行以下操作:

import zio.clock.Clock
import zio.duration._
import zio.test.environment.TestClock
import zio.test.{ assertCompletes, suite, testM, DefaultRunnableSpec }
import zio._

object ExampleSpec extends DefaultRunnableSpec {

  trait ModuleA {
    def moduleA: ModuleA.Service
  }

  object ModuleA {
    trait Service {
      def schedule(arg: Int): RIO[Clock, Unit]
    }
  }

  trait ModuleALive extends ModuleA {

    def moduleB: ModuleB.Service

    override def moduleA: ModuleA.Service = new ModuleA.Service {
      override def schedule(arg: Int): RIO[Clock, Unit] =
        moduleB.run(arg).repeat(Schedule.spaced(1.day)).map(_ => ())
    }
  }

  trait ModuleB {
    def moduleB: ModuleB.Service
  }

  object ModuleB {
    trait Service {
      def run(arg: Int): RIO[Clock, Unit]
    }
  }

  trait ModuleBTest extends ModuleB {
    val counter: Ref[Int]
    val invocations: Queue[Int]
    override def moduleB: ModuleB.Service = new ModuleB.Service {
      override def run(arg: Int): UIO[Unit] =
        counter.updateAndGet(_ + 1).flatMap(invocations.offer).unit
    }
  }

  object ModuleATest {
    def apply(ref: Ref[Int], queue: Queue[Int]): ModuleALive with ModuleBTest =
      new ModuleALive with ModuleBTest {
        val counter     = ref
        val invocations = queue
      }
  }

  def verifyExpectedInvocationCount(invocations: Queue[Int], expected: Int): Task[Unit] =
    invocations.take.flatMap { actual =>
      if (actual == expected)
        ZIO.succeed(())
      else
        ZIO.fail(new Exception(s"expected invocation count: $expected but was $actual"))
    }

  def spec = suite("a")(
    testM("a should correctly schedule b") {
      for {
        counter     <- Ref.make(0)
        invocations <- Queue.unbounded[Int]
        moduleATest = ModuleATest(counter, invocations)
        _           <- moduleATest.moduleA.schedule(42).fork
        _           <- TestClock.adjust(12.hours)
        _           <- verifyExpectedInvocationCount(invocations, 1)
        _           <- TestClock.adjust(12.hours)
        _           <- verifyExpectedInvocationCount(invocations, 2)
      } yield assertCompletes
    }
  )
}

由于我们要等待多种效果完成,因此我使用Queue来协调它们。还有两点需要注意:

  • 您可以verifyExpectedInvocationsCount使用ZIO Test中的断言来代替该方法,以获得更好的错误报告。
  • 本示例使用旧的环境编码。使用层,组合这些服务并为其中之一交换测试实现将变得更加容易。
  • 如果要测试效果不会终止(例如,如果您没有等待足够的时间,则永远不会在队列中放置另一个值),则可以使用TestAspect#nonTermination

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

如何在具有数据库调用的ASP.net MVC中正确测试控制器

来自分类Dev

我如何在lua解释器中正确调用异常

来自分类Dev

如何在Ruby中正确编写仅在创建时验证

来自分类Dev

如何在Rails中正确验证Webhook网址?

来自分类Dev

如何在单元测试中正确使用IoC?

来自分类Dev

如何在WatchKit应用中正确测试通知?

来自分类Dev

如何在Swift中正确测试核心数据

来自分类Dev

如何在Python中正确覆盖和调用超级方法

来自分类Dev

如何在类库中正确调用P / Invoke方法?

来自分类Dev

如何在if语句中正确进行等效性测试?

来自分类Dev

如何在Mocha的函数中正确放置测试套件?

来自分类Dev

如何在递归javascript调用中正确设置return语句?

来自分类Dev

如何在并行Go测试中正确使用通道?

来自分类Dev

如何在Java中正确验证PNG标头?

来自分类Dev

如何在Python脚本中正确管理API调用限制?

来自分类Dev

如何在PHP中正确调用gmdate()?

来自分类Dev

如何在for循环中正确调用递归函数?

来自分类Dev

如何在for循环中正确调用递归函数?

来自分类Dev

如何在for循环中正确调用递归函数?

来自分类Dev

如何在具有数据库调用的ASP.net MVC中正确测试控制器

来自分类Dev

我如何在lua解释器中正确调用异常

来自分类Dev

如何在汇编中正确调用函数

来自分类Dev

如何在C#中正确验证TextBox?

来自分类Dev

如何在Rails中正确验证Webhook网址?

来自分类Dev

如何在单元测试中正确使用IoC?

来自分类Dev

如何在Play Scala中正确验证和转换JSON?

来自分类Dev

如何在子组件中正确调用传递的函数

来自分类Dev

如何在 app.yaml 中正确路由目录调用?

来自分类Dev

如何在phpspreadsheet中正确调用getDefaultRowHeightByFont()函数

Related 相关文章

  1. 1

    如何在具有数据库调用的ASP.net MVC中正确测试控制器

  2. 2

    我如何在lua解释器中正确调用异常

  3. 3

    如何在Ruby中正确编写仅在创建时验证

  4. 4

    如何在Rails中正确验证Webhook网址?

  5. 5

    如何在单元测试中正确使用IoC?

  6. 6

    如何在WatchKit应用中正确测试通知?

  7. 7

    如何在Swift中正确测试核心数据

  8. 8

    如何在Python中正确覆盖和调用超级方法

  9. 9

    如何在类库中正确调用P / Invoke方法?

  10. 10

    如何在if语句中正确进行等效性测试?

  11. 11

    如何在Mocha的函数中正确放置测试套件?

  12. 12

    如何在递归javascript调用中正确设置return语句?

  13. 13

    如何在并行Go测试中正确使用通道?

  14. 14

    如何在Java中正确验证PNG标头?

  15. 15

    如何在Python脚本中正确管理API调用限制?

  16. 16

    如何在PHP中正确调用gmdate()?

  17. 17

    如何在for循环中正确调用递归函数?

  18. 18

    如何在for循环中正确调用递归函数?

  19. 19

    如何在for循环中正确调用递归函数?

  20. 20

    如何在具有数据库调用的ASP.net MVC中正确测试控制器

  21. 21

    我如何在lua解释器中正确调用异常

  22. 22

    如何在汇编中正确调用函数

  23. 23

    如何在C#中正确验证TextBox?

  24. 24

    如何在Rails中正确验证Webhook网址?

  25. 25

    如何在单元测试中正确使用IoC?

  26. 26

    如何在Play Scala中正确验证和转换JSON?

  27. 27

    如何在子组件中正确调用传递的函数

  28. 28

    如何在 app.yaml 中正确路由目录调用?

  29. 29

    如何在phpspreadsheet中正确调用getDefaultRowHeightByFont()函数

热门标签

归档