Started coding in Scala fairly recently and I tried to write some property based test-cases. Here, I am trying to generate raw data which mimics the system I am testing. The goal is to first generate base elements (ctrl and idz), then use those values to generate two classes (A1 and B1) and finally check their properties. I first tried the following -
import org.scalatest._
import prop._
import scala.collection.immutable._
import org.scalacheck.{Gen, Arbitrary}
case class A(
controller: String,
id: Double,
x: Double
)
case class B(
controller: String,
id: Double,
y: Double
)
object BaseGenerators {
val ctrl = Gen.const("ABC")
val idz = Arbitrary.arbitrary[Double]
}
trait Generators {
val obj = BaseGenerators
val A1 = for {
controller <- obj.ctrl
id <- obj.idz
x <- Arbitrary.arbitrary[Double]
} yield A(controller, id, x)
val B1 = for {
controller <- obj.ctrl
id <- obj.idz
y <- Arbitrary.arbitrary[Double]
} yield B(controller, id, y)
}
class Something extends PropSpec with PropertyChecks with Matchers with Generators{
property("Controllers are equal") {
forAll(A1, B1) {
(a:A,b:B) =>
a.controller should be (b.controller)
}
}
property("IDs are equal") {
forAll(A1, B1) {
(a:A,b:B) =>
a.id should be (b.id)
}
}
}
Running sbt test in terminal gave me the following -
[info] Something:
[info] - Controllers are equal
[info] - IDs are equal *** FAILED ***
[info] TestFailedException was thrown during property evaluation.
[info] Message: 1.1794559135007427E-271 was not equal to 7.871712821709093E212
[info] Location: (testnew.scala:52)
[info] Occurred when passed generated values (
[info] arg0 = A(ABC,1.1794559135007427E-271,-1.6982696700585273E-23),
[info] arg1 = B(ABC,7.871712821709093E212,-8.820696498155311E234)
[info] )
Now it's easy to see why the second property failed. Because every time I yield A1 and B1 I'm yielding a different value for id and not for ctrl because it is a constant. The following is my second approach wherein, I create nested for-yield to try and accomplish my goal -
case class Popo(
controller: String,
id: Double,
someA: Gen[A],
someB: Gen[B]
)
trait Generators {
val obj = for {
ctrl <- Gen.alphaStr
idz <- Arbitrary.arbitrary[Double]
val someA = for {
x <- Arbitrary.arbitrary[Double]
} yield A(ctrl, idz, someA)
val someB = for {
y <- Arbitrary.arbitrary[Double]
} yield B(ctrl, idz, y)
} yield Popo(ctrl, idz, x, someB)
}
class Something extends PropSpec with PropertyChecks with Matchers with Generators{
property("Controllers are equal") {
forAll(obj) {
(x: Popo) =>
forAll(x.someA, x.someB) {
(a:A,b:B) =>
a.controller should be (b.controller)
}
}
}
property("IDs are equal") {
forAll(obj) {
(x: Popo) =>
forAll(x.someA, x.someB) {
(a:A,b:B) =>
a.id should be (b.id)
}
}
}
}
Running sbt test in the second approach tells me that all tests pass.
[info] Something:
[info] - Controllers are equal
[info] - IDs are equal
[info] ScalaTest
[info] Run completed in 335 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
Is there a better/alternative way to reproduce my desired results? Nesting forAll seems rather clunky to me. If I were to have R -> S -> ... V -> W in my dependency graph for objects sharing elements then I'll have to create as many nested forAll.
"IDs are equal"seems to say that any pair of anAand aBshould have the sameid, which doesn't seem like a meaningful property. My guess is that you want to have some data type that hasAandBmembers (notGen[A]andGen[B]) and then your properties will be about relationships between the members of that data type. - Travis BrownAandBwhere thecontrollerandidis the same, whether the functionGaGasatisfies a valid property. So, in a sense, I do need a datatype which createsGen[A], Gen[B]to test the validity of properties ofGaGa. - Ic3fr0g