如何在scalatest中使用forAll只生成一个生成器的一个对象?

时间:2016-01-26 14:30:49

标签: scala generator scalatest scalacheck

我正在使用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
      }
    }

但我不确定它到底意味着什么。 我想要的是为每个场景生成发票并对其进行一些操作...... 我不知道为什么我关心如果生成工作或没有工作...我只是想生成一个对象来工作。

2 个答案:

答案 0 :(得分:1)

我认为您希望使用Scalacheck的方式(仅生成一个对象并为其执行测试)会破坏基于属性的测试的目的。让我详细解释一下:

经典单元测试中,您将生成受测试的系统,无论是对象还是依赖对象系统,都有一些固定数据。这可以是例如像&#34; foo&#34;和&#34; bar&#34;或者,如果你需要一个名字,你会使用类似&#34; John Doe&#34;。对于整数和其他数据,您也可以随机选择一些值。

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

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

我从未真正使用过Scalacheck,但是一位同事向我解释说它还试图覆盖边缘情况,例如将0和MAX_INT放入正整数,或者将前面的字符串放入空字符串中。 20个字符。

所以,总结一下:对一个通用对象只运行一次基于属性的测试是错误的。相反,一旦你拥有了发电机基础设施,就拥有你拥有的优势,并让你的代码被检查很多次!

答案 1 :(得分: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替换为默认值。

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

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

minSuccessful具有非常不同的含义:scalacheck运行的成功测试的最小次数 - 绝不暗示

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

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

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

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

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