Scalacheck will not report this event correctly

I wrote the following specification

"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) } } 

This is very clear in their field.

The test is successful, but when I make it fail (for example, by commenting on one of the if ), ScalaCheck correctly reports an error, but the message does not correctly mention the actual value used to evaluate the sentence, More specifically, I get:

 [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] ) 

where you see arg0 = "" // 4 shrinks , the value is not displayed.

I tried to add even a simple println statement to view cases, but the result seems to be cropped. I get something like this

 192.168.0.1 189.168. 189. 1 

Decision

 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) } } } 
+6
source share
1 answer

This is due to the possibility of simplifying the validation of ScalaCheck. ScalaCheck just sees your generator producing a string value. Whenever he finds a value that makes your property false, he tries to simplify that value. In your case, this simplifies it four times until the empty string ends, which still makes your property false.

So this is the expected, albeit confusing behavior. But you can improve the situation in three different ways.

You can choose a different data structure to represent your IP addresses. This will allow ScalaCheck to simplify your test cases in a smarter way. For example, use the following generator:

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

Now ScalaCheck knows that your generator generates only lists of length 4 and that it contains only numbers from 0 to 255. The process of simplifying the test case will take this into account, and not create values โ€‹โ€‹that the generator could not be created from the beginning. Instead, you can convert the string to your property.

The second method is to add a filter directly to your generator, which tells ScalaCheck what the IP address string should look like. This filter is used during the simplification of the test case. Define a function that checks valid strings and attaches them to an existing generator as follows:

 def validIP(ip: String): Boolean = ... val validAddrs = addrs.suchThat(validIP) forAll(validAddrs) { ... } 

The third way is to simply disable the test case simplification function in general, using forAllNoShrink instead of forAll :

 Prop.forAllNoShrink(addrs) { ... } 

I should also note that the first two methods require the ScalaCheck> = 1.11.0 version to function properly.

UPDATE:

The length of the listOfN list is not actually followed by shrinking, due to https://github.com/rickynils/scalacheck/issues/89 . I hope this can be fixed in a future version of ScalaCheck.

+5
source

Source: https://habr.com/ru/post/958277/


All Articles