3.0 ZIO测试

3.0 测试ZIO程序

除了编写表达我们期望的逻辑的程序外,测试这些程序以确保其行为符合我们的期望也很重要。
这对于ZIO及其生态系统中的库尤其重要,因为ZIO的主要重点是可组合性,这意味着我们可以从少量构件和操作中组合出针对更复杂问题的解决方案,以将它们组合在一起。
仅当每个构造块和操作员都对预期行为的保证表示担保时,才可以执行此操作。
因此我们需要保证:方案提供的是我们想要的 例如,考虑这个简单的程序。

  import zio.ZIO
  def safeDivision(x: Int, y: Int): ZIO[Any, Unit, Int] =
    ZIO.effect(x / y).catchAll(t => ZIO.fail(()))

该程序只是尝试将两个整数相除,如果已定义则返回结果,如果未定义则返回失败,例如,因为我们被零除。
如果我们熟悉上一章中的运算符,则该程序看起来非常简单,并且其行为对于我们来说似乎很明显。 但是我们能够以这种方式进行推理的唯一原因是,每个构造函数和将它们组合在一起的每个运算符都具有一定的保证。
在上面的示例中,effect构造函数保证它会捕获在评估其参数时抛出的任何非致命异常,并将该异常作为ZIO效果的失败结果返回,否则,返回将其计算结果作为ZIO成功的结果。

effect构造函数必须捕获任何非致命异常,无论其类型如何,并且每次都必须捕获异常。如果没有抛出异常,它必须返回其计算的结果,并且不能以任何方式更改该结果。
这可以使我们推断出,当我们在ZIO.effect构造函数中对两个数字进行除法时,如果得到除法结果,则将获得成功的ZIO效果,如果计算过程中抛出任何错误,则将获得错误的没有正确值ZIO effect。
同样,catchAll操作需要一个错误处理函数,并把函数应用于错误的结果,如果原始效果失败,则返回错误处理程序的结果,如果成功,则返回原始效果的成功结果。
在这种情况下,这些保证相对明显,我们甚至可能无需考虑它们。但是,随着我们在本书中学习得更多,我们将看到ZIO的很多功能来自它给我们的保证,这些保证并不那么明显,例如,如果获取了资源,它将始终被释放,或者如果我们的程序被中断它的所有部分都将立即尽快关闭。
这些强大的保证,以及理解我们一起编写不同程序时如何应用这些保证,是我们构建复杂程序的方式,这些程序对于我们自己和我们的用户仍然具有非常强大的安全性和效率性。因此,能够通过测试来验证我们正在创建的组件确实确实兑现了我们认为至关重要的保证。
当然,Scala中已经有各种其他测试框架。例如,以下是我们如何使用ScalaTest库测试简单的断言的方法,您可能已经在有关Scala的入门课程之一中学到了这一点。

import org.scalatest._
class ExampleSpec extends FunSuite {
    test("addition works") {
        assert(1 + 1 === 2) 
    }
}

这行得通,但是我们想去测试ZIO effect会遇到一些麻烦。 例如,这是测试有关ZIO效果的简单断言的初步尝试。

  class ExampleSpec2 extends FunSuite {
    test("addition works") {
      assert(ZIO.succeed(1 + 1) === 2)
    }
  }

可以编译,但是该测试没有任何意义,并且会始终失败,因为我们正在比较两种完全不相关的类型。 左侧是ZIO效果,它是并发程序的蓝图,该程序最终将返回Int,而右侧仅是Int。
我们真正想要的不是说ZIO.succeed(1 +1)等于2,而是求ZIO.succeed(1 +1)的结果等于2。
我们可以通过创建ZIO运行时并使用其unsafeRun方法运行ZIO效果,将并发程序的ZIO蓝图转换为实际运行该程序的结果来表达这一点。 这类似于ZIO App特性在上一章中自动为我们所做的。

  import zio.Runtime
  val runtime: Runtime[Any] = Runtime.default
  // runtime: Runtime[Any] = zio.Runtime$$anon$3@f4e6c0e
  class ExampleSpec3 extends FunSuite {
    test("addition works") {
      assert(runtime.unsafeRun(ZIO.succeed(1 + 1)) === 2)
    }
  }

该测试现在很有意义,可以照原样通过,但是仍然存在一些问题。
首先,这在Scala.js上根本不起作用。 unsafeRun方法运行ZIO效果以产生一个值,这意味着它需要一直阻塞直到结果可用,但我们无法在Scala.js上阻塞!我们可以使用更复杂的方法,在ZIO效果上运行一个scala.concurrent.Future,然后与ScalaTest中的 函数式接口 以对Future值进行断言,但是我们已经在这里引入了相当多的复杂性,因为它应该是简单的测试。
另外,还有一个更根本的问题。 ScalaTest对ZIO,其环境类型,其错误类型或ZIO支持的任何操作一无所知。因此,在实现与测试相关的功能时,ScalaTest无法利用ZIO的任何功能。
例如,ScalaTest具有用于使耗时太长的测试超时的功能。但是由于正如我们在上一章中了解到的那样,“未来”是不可中断的,因此“超时”只是在指定的持续时间之后无法通过测试。测试仍在运行在后台,可能会消耗系统资源。

ZIO支持中断,但是ScalaTest的超时无法与这种中断集成,因为ScalaTest对ZIO一无所知,除非我们手动完成一些管道工作,否则我们将无法进行自己的工作。
从根本上讲,问题在于大多数测试库将 effect 视为二等公民。 它们只是运行以生成“真实”值(例如Int,String或可能是Future)的东西,而这些正是测试框架可以理解的东西。
这样的结果是,当我们使用这样的测试框架编写测试时,最终放弃了ZIO的所有功能,这很痛苦,因为我们刚刚习惯于使用ZIO的功能和可编写性来编写生产代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值