简体   繁体   English

Scala 用于实现子类的特征测试

[英]Scala Testing of Trait Applied to Implementing Sub-Classes

Given a scala trait with implementing sub-classes给定具有实现子类的 scala 特征

trait Foo {
  def doesStuff() : Int
}

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

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

How do the unit tests get organized in order to test the trait and then apply the tests to each of the implementations?如何组织单元测试以测试特征,然后将测试应用于每个实现?

I'm looking for something of the form:我正在寻找以下形式的东西:

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

Which would then be applied to each of the implementing classes:然后将其应用于每个实现类:

FooSpec(Bar())

FooSpec(Baz())

If the implementations of Bar.doesStuff and Baz.doesStuff have different behavior, having two separate tests is the appropriate solution. 如果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
  }

}

However, if they have the same behavior, you can refactor the tests with a function to avoid duplicate code. 但是,如果它们具有相同的行为,则可以使用一个函数来重构测试以避免重复的代码。 I don't believe scalatest can achieve this reuse pattern at the spec level like you're asking for. 我不认为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())
  }

}

Property-based testing can do exactly what you're looking for though. 基于属性的测试可以完全满足您的需求。 Here's an example using scalacheck: 这是使用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
  }

}

Unlike ScalaTest specs, properties are always functions. 与ScalaTest规范不同,属性始终是函数。 The forAll function takes a generator, samples values of the generator and runs the test on all samples. forAll函数使用一个生成器,对该生成器的值进行采样,并对所有采样进行测试。 Our generator will always return either an instance of Bar or Baz which means the property will cover all the cases you're looking to test. 我们的生成器将始终返回BarBaz的实例,这意味着该属性将涵盖您要测试的所有情况。 forAll asserts that if a single test fails the entire property fails. forAll断言,如果单个测试失败,则整个属性都会失败。

Whilst the accepted answer definitely works, scalacheck runs 100 unit tests by default, and is typically used to tackle a different issue than the question is suggesting.虽然公认的答案肯定有效,但 scalacheck 默认运行 100 个单元测试,通常用于解决与问题所暗示的不同的问题。

Perhaps this wasn't available in 2019, but I would like to contribute an answer that as of ScalaTest 3.2.9 (and perhaps earlier) is more applicable.也许这在 2019 年不可用,但我想提供一个答案,从 ScalaTest 3.2.9(也许更早)开始更适用。

You can create a trait to house all the re-usable tests and then extend that into your unit tests.您可以创建一个特征来容纳所有可重用的测试,然后将其扩展到您的单元测试中。 I will give a brief example below using the question classes.我将在下面使用问题类给出一个简短的示例。 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)
}

Excerpts from the link in case it doesn't work链接的摘录以防它不起作用

Sometimes you may want to run the same test code on different fixture objects.有时您可能希望在不同的夹具对象上运行相同的测试代码。 In other words, you may want to write tests that are "shared" by different fixture objects.换句话说,您可能想要编写由不同夹具对象“共享”的测试。 To accomplish this in a AnyFlatSpec, you first place shared tests in behavior functions.要在 AnyFlatSpec 中完成此操作,您首先将共享测试放在行为函数中。 These behavior functions will be invoked during the construction phase of any AnyFlatSpec that uses them, so that the tests they contain will be registered as tests in that AnyFlatSpec.这些行为函数将在任何使用它们的 AnyFlatSpec 的构造阶段被调用,因此它们包含的测试将被注册为该 AnyFlatSpec 中的测试。 For example, given this stack class:例如,给定这个堆栈 class:

... ...

You may want to test the Stack class in different states: empty, full, with one item, with one item less than capacity, etc. You may find you have several tests that make sense any time the stack is non-empty.您可能想在不同的状态下测试堆栈 class:空、满、有一项、有一项小于容量等。您可能会发现,只要堆栈非空,您就有几个有意义的测试。 Thus you'd ideally want to run those same tests for three stack fixture objects: a full stack, a stack with a one item, and a stack with one item less than capacity.因此,理想情况下,您希望对三个堆栈夹具对象运行相同的测试:一个完整的堆栈、一个包含一项的堆栈以及一个包含一项小于容量的堆栈。 With shared tests, you can factor these tests out into a behavior function, into which you pass the stack fixture to use when running the tests.使用共享测试,您可以将这些测试分解为行为 function,在其中传递堆栈夹具以在运行测试时使用。 So in your AnyFlatSpec for stack, you'd invoke the behavior function three times, passing in each of the three stack fixtures so that the shared tests are run for all three fixtures.因此,在堆栈的 AnyFlatSpec 中,您将调用行为 function 三次,传入三个堆栈夹具中的每一个,以便为所有三个夹具运行共享测试。 You can define a behavior function that encapsulates these shared tests inside the AnyFlatSpec that uses them.您可以定义一个行为 function 将这些共享测试封装在使用它们的 AnyFlatSpec 中。 If they are shared between different AnyFlatSpecs, however, you could also define them in a separate trait that is mixed into each AnyFlatSpec that uses them.但是,如果它们在不同的 AnyFlatSpec 之间共享,您还可以将它们定义在一个单独的特征中,该特征混合到使用它们的每个 AnyFlatSpec 中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM