5

以前のテストの結果に依存する多くのテストを使用して、 Specs2テストを設計するための好ましい方法はありますか?

以下に、現在のテスト スイートを示します。varテストフラグメントの間にある s は好きではありません。ただし、一部のテストでは、後続のテストで再利用される ID 番号が生成されるため、それらは「必要」です。

  1. 代わりに ID 番号を Specs2 コンテキストに保存するか、すべての変更可能な状態を保持する別のオブジェクトを作成する必要がありますか? そして、仕様オブジェクトにテスト フラグメントのみを配置しますか? それとももっと良いアプローチがありますか?

  2. テストが失敗した場合、同じ深さで残りのテストをキャンセルしたいと思います。テストフラグメントを相互に依存させることはできますか? (変更可能なテストを使用するかorSkipを使用して、単一のテスト フラグメント内の残りのマッチャーをキャンセルできることはわかっていますが、フラグメント全体をキャンセルするにはどうすればよいでしょうか?)

.

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...
  }

}
4

3 に答える 3

4

質問には2つの部分があります。中間状態を格納するためのvarsの使用と、失敗した場合の例の停止です。

1-変数の使用

可変仕様を使用する場合、varsを使用する代わりの方法がいくつかあります。

lazy valsプロセスのステップを表すために使用できます。

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)
}

これらの値は遅延しているため、例が実行されたときにのみ呼び出されます。

現在の仕様の構造を変更する準備ができている場合は、最新の1.12.3-SNAPSHOTを使用して、これらすべての小さな期待を1つの例にグループ化することもできます。

"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))
  }
}

これらの期待のいずれかが失敗した場合、残りは実行されず、次のような失敗メッセージが表示されます。

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 / ThenGivenの方法で仕様を記述し、内部とWhenステップで「スローされた」期待を使用することです。ユーザーガイドでわかるように、Given/When/Then手順は文字列からデータを抽出し、入力された情報を次の情報に渡します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文字列の説明から抽出するものがない場合に使用する必要がなくなります。

その場合、次のように書くだけで十分です。

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

もう1つの可能性は、直接書き込みを許可することです。

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

オブジェクトにはすでにタイプの値が含まれているため、Given[T]からオブジェクトを作成できます。これは「Given」になります。String => MatchResult[T]MatchResult[T]T

2-失敗した例の後で実行を停止します

暗黙のWhenFail Aroundコンテキストを使用することは、確かにあなたが望むことを行うための最良の方法です(G / W / Tの例の上に示されているような期待の説明に従わない限り)。

に注意してくださいstep(stepOnFail = true)

並行例の前のブロックの1つの例が失敗したstep(stepOnFail = true)場合、次の例を中断することによって機能します。ただし、を使用している場合、その前のブロックは1つの例に限定されます。したがって、あなたが見ているもの。実際、これはバグであり、シーケンシャルを使用しているかどうかに関係なく、残りのすべての例を実行するべきではないと思います。ですから、今週の終わりに予定されている修正にご期待ください。sequential

于 2012-11-02T13:19:48.013 に答える
1

(質問 1 に関して: 例の中の s に代わるより良い方法があるかどうかはわかりませんvar。おそらく、私の例は単純に長すぎるので、Spec:s を多くの小さな仕様に分割する必要があります。)

質問 2 に関して、etorreborre によるこのメールで、後続のテストを次のように停止できることがわかりました。

"ex1" >> ok
"ex2" >> ok
"ex3" >> ko
 step(stopOnFail=true)

"ex4" >> ok

(ex1、ex2、または ex3 が失敗した場合、ex4 はスキップされます。(ただし、シーケンシャル スペックを使用している場合、これは Specs2 < 1.12.3 では期待どおりに機能しません。))


別の方法は次のとおりです:この Specs2 Googl グループ etorreborre による電子メールによると、次のように失敗時に後続のテストを停止させることができます: (「example2」はスキップされますが、「example3」と「4」は実行されます)

class TestSpec extends SuperSpecification {

    sequential

    "system1" >> {
      implicit val stop = WhenFail()
      "example1" >> ko
      "example2" >> ok
    }
    "system2" >> {
      implicit val stop = WhenFail()
      "example3" >> ok
      "example4" >> ok
    }
}

case class WhenFail() extends Around {
  private var mustStop = false

  def around[R <% Result](r: =>R) = {
    if (mustStop)          Skipped("one example failed")
    else if (!r.isSuccess) { mustStop = true; r }
    else                   r
  }
}

etorreborreによるこのメールには、仕様のリストを含めた場合、例が失敗した場合に後続の仕様をキャンセルする方法があります。

sequential ^ stopOnFail ^
"These are the selenium specifications"         ^
  include(childSpec1, childSpec2, childSpec3)

build.sbtまた、テスト オプションを編集して、子仕様が含まれた後に個別に再実行されないようにする必要があります。メールから:

 testOptions := Seq(Tests.Filter(s =>
  Seq("Spec", "Selenium").exists(s.endsWith(_)) &&
    ! s.endsWith("ChildSpec")))
于 2012-11-02T03:03:06.953 に答える
0

Specs docには、失敗した場合に.orSkipを使用して残りの例をスキップできると記載されています

"The second example will be skipped" >> {
    1 === 2
   (1 === 3).orSkip
}

しかし、私はそれを個人的に試していません

于 2012-11-01T16:40:51.537 に答える