无法通过提前 TestClock 来测试具有指数重试的 ZIO HTTP 客户端

问题描述 投票:0回答:2

我正在使用 ZIO HTTP 3.0.0-RC6(传递性地拉取 ZIO 2.0.21,但我通过指定 ZIO 版本手动驱逐它)、ZIO 和 ZIO-test 2.1-RC1 以及 Scala 2.13。我的套件看起来像这样(第二个测试的重点是测试重试,因此在 127.0.0.1:80 上没有任何监听):

  object IpGetterSpec extends ZIOSpecDefault {
    val schedule = Schedule.exponential(10.milliseconds) && 
        Schedule.recurs(2).tapOutput(o => ZIO.logInfo(s"retrying $o"))
    def spec = suite("IpGetterSpec")(
      test("test retry exponential") {
        for {
          fiber <- ZIO.fail("fail").retry(schedule).fork
          _ <- TestClock.adjust(100.milliseconds)
          res <- fiber.join.exit
        } yield assertTrue(res.isFailure)
      },
      test("request fails after being tried three times") {
        for {
          client <- ZIO.service[Client]
          fiber  <- client.url(URL.decode("http://127.0.0.1:80/?format=json").toOption.get).get("/").retry(schedule).fork
          _ <- TestClock.adjust(100.milliseconds)
          res <- fiber.join.exit
        } yield assertTrue(res.isFailure)
      }
    ).provideShared(Client.default, Scope.default)
  }

第一个测试运行,第二个测试无限期挂起。有趣的是,如果我用实时时钟重写第二个测试,如下所示:

  test("request fails after being tried three times") {
    for {
      client <- ZIO.service[Client]
      res  <- client.url(URL.decode("http://127.0.0.1:80/?format=json").toOption.get).get("/").retry(schedule).exit
    } yield assertTrue(res.isFailure)
  } @@ TestAspect.withLiveClock

它有效。

问题:这和 ZIO HTTP 客户端有关系吗?这是一个已知问题吗?

scala zio zio-test zio-http
2个回答
0
投票

不确定这应该是评论还是答案,但是当我将它们粘贴到 zio-http 项目中时,您的测试工作正常。可能是您的版本有问题。

顺便说一句,当效果具有范围时,您应该使用 ZIO.scoped 定义该范围的开始和结束。

for {
      client <- ZIO.service[Client]
      res <- ZIO.scoped(client.url(???).get("/")) // this effect has a scope
} yield assertTrue(res.isFailure)

您想用 ZIO.scoped 包装多少当然取决于您的场景。如果您从数据库池获取了数据库连接,您可能想在完成数据库连接之前对其执行一些操作,例如进行查询。

https://zio.dev/reference/resource/scope/


0
投票

问题是,当测试使用假时钟时,

ZClient.live
正在与主机系统上的网络驱动程序交互(通过 Netty 接口)。这种交互需要时间,并且从应用程序的角度来看本质上是异步的。当您调用
get(url).retry(schedule).fork
时,您告诉 ZIO 运行该操作 + 在单独的 fork 上执行所有后续重试,然后在下一行中您告诉测试跳过时间,但很可能此时套接字甚至还没有连接更不用说开始重试周期了。然后等待光纤结束,
join
有效地阻挡当前光纤。

这就是它挂起的原因。应用程序调用失败,但您已经跳过了时间,因此等待时间进度的重试反而被卡住(验证这一点的简单检查是切换到没有任何时间依赖性的计划,例如

 recurs
)。

为了解决这个问题,您可以:

  1. 使用实时时钟,正如您所看到的,这会解锁它,因为它不会在等待时钟进展时陷入僵局。是否使用它取决于您要测试的内容,我不建议使用长重试周期,因为这会增加您的测试时间
  2. 考虑是否可以只使用
    TestClient
    来代替,在大多数情况下,
    TestClock
    无法很好地处理需要与网络等现实世界系统交互的事物,因为您无法有效地模拟现实世界中的时间。
  3. 删除此测试?我假设这只是一个最小化的示例来显示您面临的问题,但如果不是,我会质疑是否需要验证
    ZClient
    是否正常工作,并考虑根本不进行此测试,因为它只是测试第三方代码。
© www.soinside.com 2019 - 2024. All rights reserved.