如何设计具有相互依赖的测试的Specs2数据库测试? [英] How design a Specs2 database test, with interdependent tests?

查看:60
本文介绍了如何设计具有相互依赖的测试的Specs2数据库测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

是否有一些首选的方法来设计 Specs2 测试,其中许多测试取决于以前测试的结果?

Is there some preferred way to design a Specs2 test, with lots of tests that depend on the results of previous tests?

下面,您将找到我当前的测试套件.我不喜欢测试片段之间的var.不过,它们是必需的",因为某些测试生成的ID号可供后续测试重用.

Below, you'll find my current test suite. I don't like the vars inbetween the test fragments. They're "needed" though, since some tests generate ID numbers that subsequent tests reuses.

  1. 应该将ID号存储在Specs2上下文中,还是创建一个包含所有可变状态的单独Object?并仅将测试片段放置在规范对象中?还是有更好的方法?

  1. Should I perhaps store the ID numbers in a Specs2 Context instead, or create a separate Object that holds all mutable state? And place only test fragments in the specification object? Or is there some even better approach?

如果测试失败,我想在相同深度取消其余测试.我可以使测试片段相互依赖吗? (我知道我可以取消单个测试片段中的剩余匹配项(通过使用可变测试或通过 orSkip ),但是取消整个片段又如何呢?)

If a test fails, I'd like to cancel the remaining test at the same depth. Can I make the test fragments depend upon each other? (I know I can cancel remaining matchers in a single test fragment (by using mutable tests, or via orSkip), but what about cancelling whole fragments?)

.

object DatabaseSpec extends Specification {
  sequential

  "The Data Access Object" should {

    var someId = "" // These var:s feels error prone, is there a better way?

    "save an object" >> {
      someId = database.save(something)
      someId must_!= ""

      // I'd like to cancel the remaining tests, below, at this "depth",
      // if this test fragmen fails. Can I do that?
      // (That is, cancel "load one object", "list all objects", etc, below.)
    }

    "load one object" >> {
      anObject = database.load(someId)
      anObject.id must_== someId
    }

    "list all objects" >> {
      objs = database.listAll()
      objs.find(_.id == someId) must beSome
    }

    var anotherId = ""
    ...more tests that create another object, and
    ...use both `someId` and `anotherId`...

    var aThirdId = ""
    ...tests that use `someId`, `anotherId` and `aThirdId...
  }


  "The Data Access Object can also" >> {
    ...more tests...
  }

}

推荐答案

您的问题有两个部分:使用vars存储中间状态,并在出现故障时停止示例.

There are 2 parts to your question: using vars for storing intermediary state, and stopping examples when one is failing.

1-使用vars

使用可变规范时,有一些使用var的替代方法.

There are some alternatives to using vars when using a mutable specification.

您可以使用lazy vals代表过程的步骤:

You can use lazy vals representing the steps of your process:

object DatabaseSpec extends mutable.Specification { 
  sequential

  "The Data Access Object" should {

    lazy val id1    = database.save(Entity(1))
    lazy val loaded = database.load(id1)
    lazy val list   = database.list

    "save an object"   >> { id1 === 1 }
    "load one object"  >> { loaded.id === id1 }
    "list all objects" >> { list === Seq(Entity(id1)) }
  }

  object database {
    def save(e: Entity) = e.id
    def load(id: Int) = Entity(id)
    def list = Seq(Entity(1))
  }
  case class Entity(id: Int)
}

由于这些值是惰性的,因此只有在执行示例时才会调用它们.

Since those values are lazy, they will only be called when the examples are executed.

如果您准备更改当前规范的结构,则还可以使用最新的1.12.3-SNAPSHOT并将所有这些小的期望归为一个示例:

If you're ready to change the structure of your current specification you can also use the latest 1.12.3-SNAPSHOT and group all those small expectations into one example:

"The Data Access Object provides a save/load/list api to the database" >> {

  lazy val id1    = database.save(Entity(1))
  lazy val loaded = database.load(id1)
  lazy val list   = database.list

  "an object can be saved"  ==> { id1 === 1 }
  "an object can be loaded" ==> { loaded.id === id1 }
  "the list of all objects can be retrieved" ==> {
    list === Seq(Entity(id1))
  }
}

如果这些期望中的任何一个失败,那么其余的将不会执行,并且您将收到类似以下的失败消息:

If any of those expectations fail then the rest will not be executed and you will get a failure message like:

x The Data Access Object provides a save/load/list api to the database
  an object can not be saved because '1' is not equal to '2' (DatabaseSpec.scala:16)

另一种可能需要2个小的改进的方法是使用Given/When/Then步骤从字符串中提取数据,并将键入的信息传递给下一个Given/When/Then:

Another possibility, which would require 2 small improvements, would be to use the Given/When/Then way of writing specifications but using "thrown" expectations inside Given and When steps. As you can see in the User Guide, the Given/When/Then steps extract data from strings and pass typed information to the next Given/When/Then:

import org.specs2._
import specification._
import matcher.ThrownExpectations

class DatabaseSpec extends Specification with ThrownExpectations { def is = 
  "The Data Access Object should"^
    "save an object"             ^ save^
    "load one object"            ^ load^
    "list all objects"           ^ list^
  end

  val save: Given[Int] = groupAs(".*") and { (s: String) =>
    database.save(Entity(1)) === 1
    1
  }

  val load: When[Int, Int] =  groupAs(".*") and { (id: Int) => (s: String) =>
    val e = database.load(id)
    e.id === 1
    e.id
  }

  val list: Then[Int] =  groupAs(".*") then { (id: Int) => (s: String) =>
    val es = database.list
    es must have size(1)
    es.head.id === id
  }
}

我要做的改进是:

  • 捕获失败异常以将其报告为失败而不是错误
  • 无需在字符串描述中提取任何内容时就不必使用groupAs(".*") and.

在这种情况下,编写以下内容就足够了:

In that case it should be enough to write:

val save: Given[Int] = groupAs(".*") and { (s: String) =>
  database.save(Entity(1)) === 1
  1
}

另一种可能性是允许直接写:

Another possibility would be to allow to directly write:

val save: Given[Int] = groupAs(".*") and { (s: String) =>
  database.save(Entity(1)) === 1
}

可以从String => MatchResult[T]创建Given[T]对象的原因,因为MatchResult[T]对象已经包含类型为T的值,该值将成为给定".

where a Given[T] object can be created from a String => MatchResult[T] because the MatchResult[T] object already contains a value of type T, that would become a "Given".

2-示例失败后停止执行

2 - Stop the execution after a failing example

使用隐式WhenFail Around上下文无疑是完成所需操作的最佳方法(除非您按照G/W/T示例上方的期望描述进行操作).

Using the implicit WhenFail Around context is certainly the best way to do what you want (unless you go with the expectations descriptions as shown above the G/W/T example).

关于step(stepOnFail = true)

如果在上一个并发示例块中的一个示例失败,则step(stepOnFail = true)通过中断以下示例进行工作.但是,当您使用sequential时,该上一个块仅限于一个示例.因此,您所看到的.实际上,我认为这是一个错误,无论您是否使用顺序,都不应执行其余所有示例.因此,请继续关注本周末即将发布的修复程序.

The step(stepOnFail = true) works by interrupting the following examples if one example in the previous block of concurrent examples failed. However, when you're using sequential, that previous block is limited to just one example. Hence what you're seeing. Actually I think that this is a bug and that all the remaining examples should not be executed, whether you're using sequential or not. So stay tuned for a fix coming up this week-end.

这篇关于如何设计具有相互依赖的测试的Specs2数据库测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆