3.3 测试实现标准的ZIO服务

3.3 测试实现标准的ZIO服务

我们测试ZIO程序时,我们遇到的常见问题之一就是使用ZIO的标准服务来测试effect。
例如,考虑这个简单的控制台程序。

  import zio.console._
  val greet: ZIO[Console, Nothing, Unit] = for {
    name <- getStrLn.orDie
    _ <- putStrLn(s"Hello, $name!")
  } yield ()
  // greet: ZIO[Console, Nothing, Unit] = zio.ZIO$FlatMap@61f0affd

这是一个非常简单的程序,我们相信它是正确的,但是我们将如何对其进行测试?
我们可以自己运行程序并验证是否收到了预期的控制台输出,但这是非常手动的,并且可能导致对潜在的控制台输入的测试覆盖面非常小,并且随着代码库其他部分的更改而缺乏持续集成。
所以我们不想这样做。 但是我们还要如何测试呢?
getStrLn将要从控制台读取实际的行,而putStrLn将要向控制台打印实际的行,那么我们如何提供输入并验证输出正确而不实际自己做呢?

这就是控制台是环境中的一项服务的事实。 因为Console是一项服务,所以我们可以提供另一种测试实现,例如,它从已填充了适当输入的输入缓冲区“读取”行,并将行“写入”到我们可以检查的输出缓冲区。
ZIO Test就是这样做的,它提供了所有标准ZIO服务的TestConsole,TestClock,TestRandom和TestSystem实现,这些服务完全确定性地有助于测试。
ZIO Test将自动为我们的每个测试提供这些服务的副本,这使此操作极为容易。 通常,我们需要做的就是调用几个特定的“测试”方法来提供所需的输入并验证输出。
为此,让我们看一下如何测试上面的控制台程序。

  import zio.test.environment._
  object ExampleSpec extends DefaultRunnableSpec {
    def spec = suite("ExampleSpec")(
      testM("greet says hello to the user") {
        for {
          _ <- TestConsole.feedLines("Jane")
          _ <- greet
          value <- TestConsole.output
        } yield assert(value)(equalTo(Vector("Hello, Jane!\n")))
      }
    )
  }

我们现在已经从根本无法测试的程序变成了完全可以测试的程序。现在,我们甚至可以使用ZIO
Test对下面所述的基于属性的测试的支持来提供各种不同的输入,并将其包括在我们的持续集成过程中,以在此处获得很高的测试覆盖率。
请注意,每个服务都会自动为每个测试提供单独的副本,因此您在使用这些测试服务时不必担心测试之间的干扰。
对于测试并发程序特别有用的另一个测试服务是TestClock。正如在上一章中所看到的,我们通常希望将事件安排在某个指定的持续时间之后发生,例如,在一个小时内进行goShopping,并且我们想验证事件是否确实在指定的持续时间之后发生。
同样,我们面临测试的问题。我们是否需要等待一个小时,以便goShopping执行以验证其是否已正确调度?

No! TestClock允许我们确定性地测试涉及时间的效果,而无需等待实时时间过去。
这是我们可以使用TestClock测试延迟指定时间的方法的方法。

  import zio.clock._ import zio.duration._
  val goShopping: ZIO[Console with Clock, Nothing, Unit] = putStrLn("Going shopping!").delay(1.hour)
  // goShopping: ZIO[Console with Clock, Nothing, Unit] = zio.ZIO$FlatMap@7be0b150
  object ExampleSpec extends DefaultRunnableSpec {
    def spec = suite("ExampleSpec")( testM("goShopping delays for one hour") {
      for {
        fiber <- goShopping.fork
        _ <- TestClock.adjust(1.hour)
        _ <- fiber.join
      } yield assertCompletes }
    ) }

我们在这里用fork和join操作介绍了几个新概念,我们将在后面几章中更全面地了解这些概念,但是在这里fork是将goShopping作为单独的逻辑过程开始执行,而主程序流程继续进行。而
join 会等待该过程完成。
由于使用的Clock实现是TestClock,因此只有在用户通过调用诸如Adjust之类的运算符进行调整时,时间才会过去。
在这里Adjust(1.hour)使所有计划在一小时或更短时间内运行的效果立即按顺序运行,从而导致goShopping完成执行并允许程序终止。
我们在这里使用assertCompletes(这是一个始终满足的断言)来更清楚地表达我们的意图,即我们在此处测试的只是该程序已完全完成。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值