ScalaCheck收缩状态测试中的命令数据

ScalaCheck收缩状态测试中的命令数据,scala,scalacheck,Scala,Scalacheck,当使用ScalaCheck进行有状态测试时,库可以缩减查找特定bug所需的命令。与userguide中的反例类似:。但是,如果命令带有参数,并且我希望ScalaCheck也收缩命令中的数据,该怎么办?请参见下面我正在测试计数器的场景: package Counter case class Counter() { private var n = 1 def increment(incrementAmount: Int) = { if (n%100!=0) { n +=

当使用ScalaCheck进行有状态测试时,库可以缩减查找特定bug所需的命令。与userguide中的反例类似:。但是,如果命令带有参数,并且我希望ScalaCheck也收缩命令中的数据,该怎么办?请参见下面我正在测试计数器的场景:

package Counter

case class Counter() {
  private var n = 1
  def increment(incrementAmount: Int) = {
    if (n%100!=0) {
      n += incrementAmount
    }
  }
  def get(): Int = n
}
计数器编程时有一个错误。如果n%100==0,则不应以给定的数量递增。因此,如果n的值是x*100,其中x是任何正整数,则计数器不会递增。我正在使用下面的ScalaCheck状态测试测试计数器:

import Counter.Counter

import org.scalacheck.commands.Commands
import org.scalacheck.{Gen, Prop}
import scala.util.{Success, Try}

object CounterCommands extends Commands {
  type State = Int
  type Sut = Counter

  def canCreateNewSut(newState: State, initSuts: Traversable[State],
                      runningSuts: Traversable[Sut]): Boolean = true
  def newSut(state: State): Sut = new Counter
  def destroySut(sut: Sut): Unit = ()
  def initialPreCondition(state: State): Boolean = true
  def genInitialState: Gen[State] = Gen.const(1)
  def genCommand(state: State): Gen[Command] = Gen.oneOf(Increment(Gen.chooseNum(1, 200000).sample.get), Get)

  case class Increment(incrementAmount: Int) extends UnitCommand {
    def run(counter: Sut) = counter.increment(incrementAmount)
    def nextState(state: State): State = {state+incrementAmount}
    def preCondition(state: State): Boolean = true
    def postCondition(state: State, success: Boolean) = success
  }

  case object Get extends Command {
    type Result = Int
    def run(counter: Sut): Result = counter.get()
    def nextState(state: State): State = state
    def preCondition(state: State): Boolean = true
    def postCondition(state: State, result: Try[Int]): Prop = result == Success(state)
  }
}
每次选择increment命令时,它都会被给定一个介于1和200000之间的任意整数作为参数。运行测试会产生以下输出:

! Falsified after 28 passed tests.
> Labels of failing property:
initialstate = 1
seqcmds = (Increment(1); Increment(109366); Increment(1); Increment(1); Inc
  rement(104970); Increment(27214); Increment(197045); Increment(1); Increm
  ent(54892); Get => 438600)
> ARG_0: Actions(1,List(Increment(1), Increment(109366), Increment(1), Incr
  ement(1), Increment(104970), Increment(27214), Increment(197045), Increme
  nt(1), Increment(54892), Get),List())
> ARG_0_ORIGINAL: Actions(1,List(Get, Get, Increment(1), Increment(109366),
   Get, Get, Get, Get, Increment(1), Get, Increment(1), Increment(104970),
  Increment(27214), Get, Increment(197045), Increment(1), Increment(54892),
   Get, Get, Get, Get, Get, Increment(172491), Get, Increment(6513), Get, I
  ncrement(57501), Increment(200000)),List())
! Falsified after 4 passed tests.
> Labels of failing property:
initialstate = 1
seqcmds = (Increment(9); Increment(40); Get => 10)
> ARG_0: Actions(1,List(Increment(9), Increment(40), Get),List())
> ARG_0_ORIGINAL: Actions(1,List(Increment(9), Increment(34), Increment(40)
  , Get),List())
ScalaCheck确实收缩了查找bug所需的命令(如
ARG_0
中所示),但它没有收缩命令中的数据。最终得到的计数器值(438600)比查找bug实际需要的值大得多。如果第一个increment命令作为参数给出99,那么就会发现bug

在运行有状态测试时,ScalaCheck中是否有任何方法可以收缩命令中的数据?使用的ScalaCheck版本是1.14.1

编辑: 我试图简化这个bug(如果n!=10,则只增加),并添加了Levi建议的收缩器,但仍然无法使其工作。整个可运行代码如下所示:

package LocalCounter

import org.scalacheck.commands.Commands
import org.scalacheck.{Gen, Prop, Properties, Shrink}
import scala.util.{Success, Try}

case class Counter() {
  private var n = 1
  def increment(incrementAmount: Int) = {
    if (n!=10) {
      n += incrementAmount
    }
  }
  def get(): Int = n
}

object CounterCommands extends Commands {
  type State = Int
  type Sut = Counter

  def canCreateNewSut(newState: State, initSuts: Traversable[State],
                      runningSuts: Traversable[Sut]): Boolean = true
  def newSut(state: State): Sut = new Counter
  def destroySut(sut: Sut): Unit = ()
  def initialPreCondition(state: State): Boolean = true
  def genInitialState: Gen[State] = Gen.const(1)
  def genCommand(state: State): Gen[Command] = Gen.oneOf(Increment(Gen.chooseNum(1, 40).sample.get), Get)

  case class Increment(incrementAmount: Int) extends UnitCommand {
    def run(counter: Sut) = counter.increment(incrementAmount)
    def nextState(state: State): State = {state+incrementAmount}
    def preCondition(state: State): Boolean = true
    def postCondition(state: State, success: Boolean) = success
  }

  case object Get extends Command {
    type Result = Int
    def run(counter: Sut): Result = counter.get()
    def nextState(state: State): State = state
    def preCondition(state: State): Boolean = true
    def postCondition(state: State, result: Try[Int]): Prop = result == Success(state)
  }

  implicit val shrinkCommand: Shrink[Command] = Shrink({
      case Increment(amt) => Shrink.shrink(amt).map(Increment(_))
      case Get => Stream.empty
  })
}

object CounterCommandsTest extends Properties("CounterCommands") {
  CounterCommands.property().check()
}

运行代码会得到以下输出:

! Falsified after 28 passed tests.
> Labels of failing property:
initialstate = 1
seqcmds = (Increment(1); Increment(109366); Increment(1); Increment(1); Inc
  rement(104970); Increment(27214); Increment(197045); Increment(1); Increm
  ent(54892); Get => 438600)
> ARG_0: Actions(1,List(Increment(1), Increment(109366), Increment(1), Incr
  ement(1), Increment(104970), Increment(27214), Increment(197045), Increme
  nt(1), Increment(54892), Get),List())
> ARG_0_ORIGINAL: Actions(1,List(Get, Get, Increment(1), Increment(109366),
   Get, Get, Get, Get, Increment(1), Get, Increment(1), Increment(104970),
  Increment(27214), Get, Increment(197045), Increment(1), Increment(54892),
   Get, Get, Get, Get, Get, Increment(172491), Get, Increment(6513), Get, I
  ncrement(57501), Increment(200000)),List())
! Falsified after 4 passed tests.
> Labels of failing property:
initialstate = 1
seqcmds = (Increment(9); Increment(40); Get => 10)
> ARG_0: Actions(1,List(Increment(9), Increment(40), Get),List())
> ARG_0_ORIGINAL: Actions(1,List(Increment(9), Increment(34), Increment(40)
  , Get),List())

这不是最简单的示例。

您应该能够为
命令定义自定义的
收缩
,如下所示:

implicit val shrinkCommand: Shrink[Command] = Shrink({
  case Increment(amt) => shrink(amt).map(Increment(_))
  case Get => Stream.empty
}

请注意,由于Scala 2.13中不推荐使用
Stream
,因此您可能需要禁用Scala 2.13中的警告(Scalacheck 1.15将允许使用
LazyList
来定义收缩)。

我尝试将其放入CounterCommands对象中,但看起来它仍然没有收缩。它应该在其他地方或以某种方式注册吗?你好,Levi,我已经用你的shrinker在原始文本(编辑后)中添加了一个简化的示例,但我仍然无法让它工作。我是不是遗漏了什么?