Scala特征测试在实现子类中的应用

5gfr0r5j  于 2022-11-09  发布在  Scala
关注(0)|答案(2)|浏览(158)

给出了实现子类的Scala特性

trait Foo {
  def doesStuff() : Int
}

case class Bar() extends Foo { ... }

case class Baz() extends Foo { ... }

如何组织单元测试以测试特征,然后将测试应用于每个实现?

我在找这种形式的东西:

class FooSpec(foo : Foo) extends FlatSpec {
  "A Foo" should {
    "do stuff" in {
      assert(foo.doesStuff == 42)
    }
  }
}

然后将其应用于每个实现类:

FooSpec(Bar())

FooSpec(Baz())
vnjpjtjt

vnjpjtjt1#

如果Bar.doesStuffBaz.doesStuff的实现具有不同的行为,则使用两个单独的测试是合适的解决方案。

import org.scalatest.FlatSpec

class FooSpec1 extends FlatSpec {

  "a Bar" should "do a bar thing" in {
    Bar().doesStuff() == 42
  }

  "a Baz" should "do a baz thing" in {
    Baz().doesStuff() % 2 == 0
  }

}

但是,如果它们具有相同的行为,则可以使用函数重构测试以避免重复代码。我不相信scalatest能够像您所要求的那样在规范级别上实现这种重用模式。

import org.scalatest.FlatSpec

class FooSpec2 extends FlatSpec {

  def checkDoesStuff(foo: Foo): Boolean =
    foo.doesStuff() == 42

  "a Bar" should "do a bar thing" in {
    checkDoesStuff(Bar())
  }

  "a Baz" should "do a baz thing" in {
    checkDoesStuff(Baz())
  }

}

不过,基于属性的测试完全可以实现您想要的结果。下面是一个使用scalacheck的示例:

import org.scalacheck.{Gen, Properties}
import org.scalacheck.Prop.forAll

object FooProperties extends Properties("Foo"){

  val fooGen: Gen[Foo] = Gen.pick(1, List(Bar(), Baz())).map(_.head)

  property("a Foo always does stuff") = forAll(fooGen){
    (foo: Foo) => foo.doesStuff() == 42
  }

}

与ScalaTest规范不同,属性始终是函数。forAll函数接受一个生成器,对生成器的值进行采样,并对所有采样运行测试。我们的生成器将始终返回BarBaz的示例,这意味着该属性将涵盖您要测试的所有用例。forAllAssert,如果单个测试失败,则整个属性都会失败。

rsaldnfx

rsaldnfx2#

虽然公认的答案肯定有效,但scalacheck默认情况下运行100个单元测试,并且通常用于解决与问题所建议的不同的问题。
也许这在2019年还没有提供,但我想给出一个答案,即ScalaTest3.2.9(以及更早的版本)更适用。
您可以创建一个特征来容纳所有可重用的测试,然后将其扩展到您的单元测试中。下面我将使用问题类给出一个简短的例子。The full example is provided at this link under "Shared tests".

trait FooBehaviours { this: AnyFlatSpec =>
    /*
    the defined tests here should consider how your trait behaves in certain conditions
    */
    def emptyFoo(foo: => Foo): Unit = {
        it should "do stuff" in {
            assert(foo.doesStuff == 42)
        }
    }
    def fullFoo(foo: => Foo): Unit = {
        it should "do stuff" in {
            assert(foo.doesStuff == 420)
        }
    }
}

class FooSpec extends AnyFlatSpec with FooBehaviours {
    //these methods are defs but will be re-instantiated in the behaviour tests due to the call-by-name ": =>" parameters 
    def bar = Bar()
    def baz = Baz()

    behavior of "A bar"
    it should behave like emptyFoo(bar)
    it should behave like fullFoo(bar)

    behavior of "A baz"
    it should behave like emptyFoo(baz)
    it should behave like fullFoo(baz)
}

链接摘录,以防它不起作用
有时,您可能希望在不同的装置对象上运行相同的测试代码。换句话说,您可能希望编写由不同的Fixture对象“共享”的测试。要在AnyFlatSpec中实现这一点,首先要将共享测试放在行为函数中。这些行为函数将在使用它们的任何AnyFlatSpec的构造阶段被调用,因此它们包含的测试将被注册为该AnyFlatSpec中的测试。例如,给定此堆栈类:
..。
您可能希望在不同的状态下测试Stack类:空、满、一项、一项小于容量,等等。您可能会发现,只要堆栈不为空,您就有几个测试是有意义的。因此,理想情况下,您会希望对三个堆栈装置对象运行相同的测试:一个完整的堆栈,一个只有一个项目的堆栈,以及一个项目少于容量的堆栈。使用共享测试,您可以将这些测试分解到一个行为函数中,并将运行测试时使用的堆栈装备传递到该函数中。因此,在您的AnyFlatSpec for Stack中,您将调用三次Behavior函数,传入三个堆栈装备中的每一个,以便为所有三个装备运行共享测试。您可以定义一个行为函数,将这些共享测试封装在使用它们的AnyFlatSpec中。但是,如果它们在不同的AnyFlatSpec之间共享,您还可以在混合到使用它们的每个AnyFlatSpec中的单独特征中定义它们。

相关问题