我正在使用 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 客户端有关系吗?这是一个已知问题吗?
不确定这应该是评论还是答案,但是当我将它们粘贴到 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 包装多少当然取决于您的场景。如果您从数据库池获取了数据库连接,您可能想在完成数据库连接之前对其执行一些操作,例如进行查询。
问题是,当测试使用假时钟时,
ZClient.live
正在与主机系统上的网络驱动程序交互(通过 Netty 接口)。这种交互需要时间,并且从应用程序的角度来看本质上是异步的。当您调用 get(url).retry(schedule).fork
时,您告诉 ZIO 运行该操作 + 在单独的 fork 上执行所有后续重试,然后在下一行中您告诉测试跳过时间,但很可能此时套接字甚至还没有连接更不用说开始重试周期了。然后等待光纤结束,join
有效地阻挡当前光纤。
这就是它挂起的原因。应用程序调用失败,但您已经跳过了时间,因此等待时间进度的重试反而被卡住(验证这一点的简单检查是切换到没有任何时间依赖性的计划,例如
recurs
)。
为了解决这个问题,您可以:
TestClient
来代替,在大多数情况下,TestClock
无法很好地处理需要与网络等现实世界系统交互的事物,因为您无法有效地模拟现实世界中的时间。 ZClient
是否正常工作,并考虑根本不进行此测试,因为它只是测试第三方代码。