използване на specs2 със scalaz-scalacheck-binding за тестване на закони

Намирам използването на specs2 със scalacheck за проверка на законите на Monoid за малко грозно, когато се опитвам да използвам библиотеката за свързване на scalaz scalacheck. Моят код използва scalaz Monoid, така че исках да използвам техните закони, за да проверя, че моят MyType ги прилага.

Тази грозота ме кара да мисля, че пропускам нещо или неправилно използвам Specs2 или scalacheck-binding API. Предложенията се оценяват.

Ето какво направих:-

Използвам specs2 3.7 със scalaz 2.7.0

Четене на ръководството за потребителя на адрес "http://etorreborre.github.io/specs2/guide/SPECS2-3.0/org.specs2.guide.UseScalaCheck.html" Разширих спецификацията си с чертата Scalacheck и имам Arbitrary[MyType] в обхват, така че трябва да мога да използвам scalacheck OK.

Документът, споменат по-горе, гласи, че трябва да предам функция на метода prop, стига предадената функция да връща Result, където Prop на scalacheck е валидно Result

API за свързване на scalacheck ми дава monoid.laws[T] функция, която връща Properties, което е Prop, така че това трябва да е ОК, също така приема имплицитни параметри от типове Monoid[T], Equal[T] и Arbitrary[T], всички от които имам в обхват, където T е MyType

Искам да направя това:

class MyTypeSpec extends Specification with ScalaCheck {
  def is = s2"""
   MyType spec must :-
     obey the Monoid Laws $testMonoidLaws
  """

  def testMonoidLaws = {
    import org.scalacheck.{Gen, Arbitrary}
    import scalaz.scalacheck.ScalazProperties._
    implicit val arbMyType: Arbitrary[MyType] = genArbMyTpe() // an helper Arbitrary Gen func i have written
    prop { monoid.laws[MyType] }
  }
}

но prop cannot be applied to (org.scalacheck.Properties) Изисква T в Arbitrary да бъде типът в параметъра на функцията, така че направих това, забележете, че изхвърлям параметъра t, ...

class MyTypeSpec extends Specification with ScalaCheck {
  def is = s2"""
   MyType spec must :-
     obey the Monoid Laws $testMonoidLaws
  """

  def testMonoidLaws = {
    import org.scalacheck.{Gen, Arbitrary}
    import scalaz.scalacheck.ScalazProperties._
    implicit val arbMyType: Arbitrary[MyType] = genArbMyTpe() //some Arbitrary Gen func
    prop { (t: Path => monoid.laws[MyType] }
  }
}

Тестът ми мина. ура! Та какъв е проблема?

Не ми е приятно за теста. Всичко, което казва, е, че е преминало. Не получавам резултат, както бих, ако използвам Scalacheck директно, казвайки ми кои закони изпълнява и приема. Също така изхвърлям параметъра t и оставям monoid.laws[MyType] да намери имплицитните елементи в обхвата, което просто изглежда грешно. Работи ли? Объркал ли съм Specs2 API?

модифициране на MyType, така че определено да се провали законите причиниха теста да се провали, което е добре, но все още съм неспокоен, тъй като винаги се проваля с

Falsified after 0 passed tests.

Мога да събера Arbitrary[MyType] чрез правене

prop { (p: Path) => monoid.laws[Path] }.collectArg(f => "it was " + f.shows)

след това го стартирайте така

sbt testOnly MyTypeSpec -- scalacheck.verbose

който ми показва събраните стойности на t, когато работи, но докато изхвърлям t, не съм сигурен дали това изобщо е валидно.

Има ли по-добър начин за тестване с помощта на Specs2 и scalaz scalacheck-bindings, който е по-малко грозен и извежда информация, която ми дава увереност, че законите са изпробвани и тествани?

Благодаря

Карл


person Karl    schedule 06.01.2016    source източник


Отговори (1)


Можете да използвате Properties директно, без да се налага да използвате prop. Ето пълен пример:

import org.specs2._
import scalaz.scalacheck.ScalazProperties._
import org.scalacheck._
import scalaz._, Scalaz._
import PositiveInt._

class TestSpec extends Specification with ScalaCheck { def is = s2"""

 PositiveInt should pass the Monoid laws $e1

"""
  def e1 = monoid.laws[PositiveInt]
}

case class PositiveInt(i: Int)

object PositiveInt {
  implicit def ArbitraryPositiveInt: Arbitrary[PositiveInt] =
    Arbitrary(Gen.choose(0, 100).map(PositiveInt.apply))

  implicit def EqualPositiveInt: Equal[PositiveInt] =
    Equal.equalA[PositiveInt]

  implicit def MonoidPositiveInt: Monoid[PositiveInt] = new Monoid[PositiveInt] {
    val zero = PositiveInt(1)
    def append(p1: PositiveInt, p2: =>PositiveInt): PositiveInt =
      PositiveInt(p1.i + p2.i)
  }
}

И тъй като екземплярът Monoid е неправилен, той ще се провали с:

[info] TestSpec
[info]
[error]  x PositiveInt should pass the Monoid laws
[error]  Falsified after 0 passed tests.
[error]  > Labels of failing property:
[error]  monoid.left identity
[error]  > ARG_0: PositiveInt(3)
[info]
[info]
[info] Total for specification TestSpec
[info] Finished in 185 ms
[info] 1 example, 1 failure, 0 error

Провалът показва първите закони, които не успяват да преминат. Той обаче не създава няколко примера, по един за всеки закон, за да покаже кой закон се изпълнява. Ако искате да направите това, можете да съпоставите всяко свойство на законите Properties към пример: class TestSpec разширява спецификацията със ScalaCheck { def is = s2"""

 PositiveInt should pass the Monoid laws $properties

"""

  def properties = toExamples(monoid.laws[PositiveInt])

  def toExamples(ps: Properties): Fragments =
    t ^ Fragments.foreach(ps.properties) { case (name, prop) => br ^ name ! prop }
}

Това отпечатва (за преминаващ екземпляр Monoid[PositiveInt]):

[info] TestSpec
[info]
[info]  PositiveInt should pass the Monoid laws
[info]   + monoid.semigroup.associative
[info]   + monoid.left identity
[info]   + monoid.right identity
[info]
[info] Total for specification TestSpec
[info] Finished in 91 ms
[info] 3 examples, 300 expectations, 0 failure, 0 error
person Eric    schedule 07.01.2016
comment
Благодаря Ерик. Не ми беше ясно, че мога просто да върна Properties директно от документацията, където всички примери използват prop. Благодаря - person Karl; 20.01.2016