【问题标题】:how to use forAll in scalatest to generate only one object of a generator?如何在 scalatest 中使用 forAll 只生成一个生成器对象?
【发布时间】:2016-09-15 00:56:05
【问题描述】:

我正在使用 scalatest 和 scalacheck,同时也在使用 FeatureSpec。

我有一个生成器类,它为我生成看起来像这样的对象:

object InvoiceGen {

  def myObj = for {

    country <- Gen.oneOf(Seq("France", "Germany", "United Kingdom", "Austria"))
    type <- Gen.oneOf(Seq("Communication", "Restaurants", "Parking"))
    amount <- Gen.choose(100, 4999)
    number <- Gen.choose(1, 10000)
    valid <- Arbitrary.arbitrary[Boolean]

  } yield SomeObject(country, type, "1/1/2014", amount,number.toString, 35, "something", documentTypeValid, valid, "")

现在,我有了与 FeatureSpec 配合使用的测试类以及运行测试所需的一切。

在这个类中我有场景,在每个场景中我想生成一个不同的对象。 据我了解,生成对象最好使用 forAll func,但对于所有人来说,都不一定会为您带来对象,因此您可以添加 minSuccessful(1) 以确保您获得列表 1 obj ... .

我是这样做的,它确实有效:

scenario("some scenario") {
      forAll(MyGen.myObj, minSuccessful(1)) { someObject =>
        Given("A connection to the system")
        loginActions shouldBe 'Connected

        When("something")
        //blabla
        Then("something should happened")
        //blabla
      }
    }

但我不确定这到底是什么意思。 我想要的是为每个场景生成一张发票并对其执行一些操作...... 我不知道为什么我关心生成是否有效……我只是想要一个生成的对象来工作。

【问题讨论】:

  • 我想你只是想取样:MyGen.myObj.sample(这是单个生成的对象)
  • @Łukasz 但这给了我和选项......而不是我的对象

标签: scala generator scalatest scalacheck


【解决方案1】:

TL;DR:要获得一个对象,并且只有一个,请使用myObj.sample.get。除非您的发电机正在做一些非常安全且不会爆炸的奇特事情。

我推测您的意图是使用一些随机生成的域对象运行某种集成/验收测试——换句话说(ab-)使用 scalacheck 作为简单的数据生成器——并且您希望minSuccessful(1) 将确保测试只运行一次。

请注意,情况并非如此!。如果失败,scalacheck 将多次运行您的测试,以尝试将输入数据缩小到最小的反例。

如果您想确保您的测试只运行一次,您必须使用sample。

但是,如果多次运行测试没问题,更喜欢 minSuccessful(1) 而不是“快速成功”,但仍然可以从最小化的反例中获益,以防测试失败。


Gen.sample 返回一个选项,因为generators can fail:

ScalaCheck 生成器可能会失败,例如,如果您要添加过滤器 (listingGen.suchThat(...)),并且该失败是使用 Option 类型建模的。

但是:

[...] 如果您确定您的生成器永远不会失败,您可以像上面示例中那样简单地调用 Option.get。或者您可以使用 Option.getOrElse 将 None 替换为默认值。

一般来说,如果您的生成器很简单,即不使用可能失败的生成器并且不使用任何过滤器,那么在 .sample 返回的选项上调用 .get 是完全安全的。我过去一直这样做,从来没有遇到过问题。如果您的生成器经常从.sample 返回None,它们很可能也会使scalacheck 无法成功生成值。

如果您只想要一个对象,请使用 Gen.sample.get。


minSuccessful 有一个非常不同的含义:这是 scalacheck 运行的成功测试的最少数量——这绝不暗示

  • scalacheck 只从生成器中取出一个值,或者
  • 测试只运行一次。

minSuccessful(1) scalacheck 想要一个成功的测试。它会从生成器中取出样本,直到测试至少运行一次——即如果您在测试主体中使用whenever 过滤生成的值,只要whenever 丢弃它们,scalacheck 就会采样。

如果测试通过,scalacheck 很高兴并且不会再次运行测试。

但是,如果测试失败,scalacheck 将尝试生成一个最小的示例以使测试失败。只要输入数据失败,它就会缩小 并运行测试,然后为您提供最小化 计数器示例,而不是触发初始失败的实际输入。

这是属性测试的一个重要属性,因为它可以帮助您发现错误:原始数据通常太大而无法进行调试。最小化它可以帮助您发现实际触发故障的输入数据,即您没有想到的空字符串等极端情况。

【讨论】:

    【解决方案2】:

    我认为您想要使用 Scalacheck 的方式(只生成一个对象并为其执行测试)违背了基于属性的测试的目的。让我详细解释一下:

    在经典单元测试中,您将生成您的被测系统,无论是对象还是依赖对象的系统,都带有一些固定数据。这可以例如是像“foo”和“bar”这样的字符串,或者,如果你需要一个名字,你可以使用像“John Doe”这样的东西。对于整数等数据,也可以随机取一些值。

    主要优点是这些是“普通”值——您可以直接在代码中看到它们并将它们与失败测试的输出相关联。最大的缺点是测试只会使用您指定的值运行,这反过来意味着您的代码也只会使用这些值进行测试。

    相比之下,基于属性的测试只允许您描述数据的外观(例如“一个正整数”、“一个最多 20 个字符的字符串”)。然后,测试框架将在生成器的帮助下生成许多匹配的对象并为所有对象执行测试。通过这种方式,您可以更加确定您的代码对于不同的输入实际上是正确的,这毕竟是测试的目的:检查您的代码是否针对可能的输入执行了应有的操作。

    我从未真正使用过 Scalacheck,但一位同事向我解释说,它也试图覆盖边缘情况,例如为正整数输入 0 和 MAX_INT,或为上述字符串输入空字符串,最大值为 。 20 个字符。

    所以,总结一下:只为一个通用对象运行一次基于属性的测试是错误的做法。相反,一旦您拥有了生成器基础架构,就可以利用您所拥有的优势,让您的代码被检查更多次!

    【讨论】:

      猜你喜欢
      • 2015-05-17
      • 2022-04-30
      • 2019-10-18
      • 2020-05-27
      • 1970-01-01
      • 1970-01-01
      • 2019-03-18
      • 1970-01-01
      • 2012-09-14
      相关资源
      最近更新 更多