12

私は次の仕様を書きました

"An IP4 address" should "belong to just one class" in {
    val addrs = for {
        a <- Gen.choose(0, 255)
        b <- Gen.choose(0, 255)
        c <- Gen.choose(0, 255)
        d <- Gen.choose(0, 255)
    } yield s"$a.$b.$c.$d"

    forAll (addrs) { ip4s =>
        var c: Int = 0
        if (IP4_ClassA.unapply(ip4s).isDefined) c = c + 1
        if (IP4_ClassB.unapply(ip4s).isDefined) c = c + 1
        if (IP4_ClassC.unapply(ip4s).isDefined) c = c + 1
        if (IP4_ClassD.unapply(ip4s).isDefined) c = c + 1
        if (IP4_ClassE.unapply(ip4s).isDefined) c = c + 1
        c should be (1)
    }
}

その範囲は非常に明確です。

テストは成功しましたが、(ステートメントの 1 つをコメントアウトするなどしてif) 強制的に失敗させた場合、ScalaCheck はエラーを正しく報告しますが、メッセージには、命題の評価に使用された実際の値が正しく記載されていません。より具体的には、次のようになります。

[info] An IP4 address
[info] - should belong to just one class *** FAILED ***
[info]   TestFailedException was thrown during property evaluation.
[info]     Message: 0 was not equal to 1
[info]     Location: (NetSpec.scala:105)
[info]     Occurred when passed generated values (
[info]       arg0 = "" // 4 shrinks
[info]     )

見えるところにarg0 = "" // 4 shrinksは値が表示されません。

ケースを確認するために簡単なステートメントを追加しようとしprintlnましたが、出力がトリミングされているようです。私はこのようなものを手に入れます

192.168.0.1
189.168.
189.
1

解決

import org.scalacheck.Prop.forAllNoShrink
import org.scalatest.prop.Checkers.check

"An IP4 address" should "belong to just one class" in {
  val addrs = for {
    a <- Gen.choose(0, 255)
    b <- Gen.choose(0, 255)
    c <- Gen.choose(0, 255)
    d <- Gen.choose(0, 255)
  } yield s"$a.$b.$c.$d"
  check {
    forAllNoShrink(addrs) { ip4s =>
      var c: Int = 0
      if (IP4.ClassA.unapply(ip4s).isDefined) c = c + 1
      if (IP4.ClassB.unapply(ip4s).isDefined) c = c + 1
      if (IP4.ClassC.unapply(ip4s).isDefined) c = c + 1
      if (IP4.ClassD.unapply(ip4s).isDefined) c = c + 1
      if (IP4.ClassE.unapply(ip4s).isDefined) c = c + 1
      c == (1)
    }
  }
}
4

1 に答える 1

12

これは、ScalaCheck のテスト ケース単純化機能が原因です。ScalaCheck は、ジェネレーターが文字列値を生成することを確認するだけです。プロパティを false にする値が見つかると、その値を単純化しようとします。あなたの場合、空の文字列になるまで4回単純化されますが、それでもプロパティはfalseになります。

したがって、これは混乱を招きますが、予想される動作です。しかし、3 つの異なる方法で状況を改善できます。

IP アドレスを表す別のデータ構造を選択できます。これにより、ScalaCheck はテスト ケースをよりインテリジェントな方法で単純化できるようになります。たとえば、次のジェネレータを使用します。

val addrs = Gen.listOfN(4, Gen.choose(0,255))

これで ScalaCheck は、ジェネレーターが長さ 4 のリストのみを生成し、0 から 255 までの数値のみを含むことを認識します。テスト ケースの単純化プロセスではこれが考慮され、ジェネレーターによって生成されなかった値は作成されません。始める。代わりに、プロパティ内で文字列への変換を行うことができます。

2 番目の方法は、フィルターをジェネレーターに直接追加することです。これは、IP アドレス文字列がどのように見えるべきかを ScalaCheck に伝えます。このフィルターは、テスト ケースの単純化中に使用されます。有効な文字列をチェックする関数を定義し、次の方法で既存のジェネレーターにアタッチします。

def validIP(ip: String): Boolean = ...

val validAddrs = addrs.suchThat(validIP)

forAll(validAddrs) { ... }

forAllNoShrink3 番目の方法は、代わりに を使用して、テスト ケースの単純化機能を完全に無効にするだけforAllです。

Prop.forAllNoShrink(addrs) { ... }

また、最初の 2 つの方法が適切に機能するには、ScalaCheck バージョン >= 1.11.0 が必要であることにも言及する必要があります。

アップデート:

リストの長さは、 https://github.com/rickynils/scalacheck/issues/89listOfNにより、シュリンカーによって実際には考慮されなくなりました。これは ScalaCheck の将来のバージョンで修正されることを願っています。

于 2013-11-18T23:59:48.733 に答える