2012-11-01 24 views
5

Có cách nào ưu tiên để thiết kế thử nghiệm Specs2, với nhiều thử nghiệm phụ thuộc vào kết quả của các thử nghiệm trước đó không?Làm thế nào thiết kế một bài kiểm tra cơ sở dữ liệu Specs2, với các bài kiểm tra phụ thuộc lẫn nhau?

Dưới đây, bạn sẽ tìm thấy bộ thử nghiệm hiện tại của mình. Tôi không thích các var s giữa các đoạn thử nghiệm. Mặc dù vậy, chúng là "cần thiết" vì một số thử nghiệm tạo ra các số ID mà các thử nghiệm tiếp theo sử dụng lại.

  1. Tôi có nên lưu trữ số ID trong Specs2 Bối cảnh thay vào đó, hoặc tạo một đối tượng riêng chứa tất cả trạng thái có thể thay đổi không? Và chỉ đặt các mảnh thử nghiệm trong đối tượng đặc tả? Hoặc là có một số cách tiếp cận tốt hơn?

  2. Nếu thử nghiệm không thành công, tôi muốn hủy thử nghiệm còn lại ở cùng độ sâu. Tôi có thể làm cho các mảnh thử nghiệm phụ thuộc vào nhau không? (Tôi biết tôi có thể hủy quẹt còn lại trong một đoạn thử nghiệm duy nhất (bằng cách sử dụng các bài kiểm tra có thể thay đổi, hoặc thông qua orSkip), nhưng những gì về việc hủy toàn bộ mảnh vỡ?)

.

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

} 
+0

Thông số BTW2 3.x đã được thiết kế để giải quyết vấn đề chính xác này, nơi bạn có thể tạo các bài kiểm tra tùy ý dựa trên kết quả của các bài kiểm tra trước đó. Xem tại đây: https://etorreborre.github.io/specs2/guide/SPECS2-3.1.1/org.specs2.guide.CreateOnlineSpecifications.html – Eric

Trả lời

4

Có 2 phần cho câu hỏi của bạn: sử dụng vars để lưu trữ trạng thái trung gian và dừng các ví dụ khi bị lỗi.

1 - Sử dụng vars

Có một số lựa chọn thay thế cho việc sử dụng vars khi sử dụng một đặc điểm kỹ thuật có thể thay đổi.

Bạn có thể sử dụng lazy vals đại diện cho các bước của quá trình của bạn:

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

Kể từ những giá trị đó là lười biếng, họ sẽ chỉ được gọi khi các ví dụ được thực thi.

Nếu bạn đã sẵn sàng để thay đổi cấu trúc của đặc điểm kỹ thuật hiện tại của bạn, bạn cũng có thể sử dụng mới nhất 1.12.3-SNAPSHOT và nhóm tất cả những mong đợi của nhỏ thành một ví dụ:

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

Nếu bất kỳ của những kỳ vọng thất bại sau đó phần còn lại sẽ không được thực hiện và bạn sẽ nhận được một thông báo lỗi như:

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) 

một khả năng khác, mà sẽ yêu cầu 2 cải tiến nhỏ, sẽ được sử dụng một cách Given/When/Then viết chi tiết kỹ thuật nhưng sử dụng "ném" expectati các tiện ích trong các bước GivenWhen. Như bạn có thể nhìn thấy trong Hướng dẫn sử dụng, các Given/When/Then bước trích xuất dữ liệu từ chuỗi và chuyển thông tin gõ tiếp theo 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 
    } 
} 

Những cải tiến, mà tôi sẽ làm, bao gồm:

  • ngoại lệ lỗi bắt báo cáo chúng là lỗi và không phải lỗi
  • xóa sự cần thiết phải sử dụng groupAs(".*") and khi không có gì để trích xuất từ ​​mô tả chuỗi.

Trong trường hợp đó nó phải là đủ để viết:

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

Một khả năng khác sẽ được cho phép để trực tiếp viết:

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

nơi một đối tượng Given[T] có thể được tạo ra từ một String => MatchResult[T] vì đối tượng MatchResult[T] đã chứa giá trị loại T, sẽ trở thành "Đã cho".

2 - Dừng việc thực hiện sau khi một ví dụ không

Sử dụng ngầm WhenFailAround bối cảnh chắc chắn là cách tốt nhất để làm những gì bạn muốn (trừ khi bạn đi với các mô tả mong đợi như trên G/W/T thí dụ).

Lưu ý về step(stepOnFail = true)

Các step(stepOnFail = true) hoạt động bằng cách làm gián đoạn các ví dụ sau nếu một ví dụ trong khối trước của ví dụ đồng thời thất bại. Tuy nhiên, khi bạn đang sử dụng sequential, khối trước đó được giới hạn chỉ là một ví dụ. Do đó những gì bạn đang nhìn thấy. Trên thực tế tôi nghĩ rằng đây là một lỗi và rằng tất cả các ví dụ còn lại không nên được thực hiện, cho dù bạn đang sử dụng tuần tự hay không. Vì vậy, hãy tiếp tục theo dõi để có một bản sửa lỗi sắp tới vào cuối tuần này.

+0

Kỳ vọng nhóm thành một ví dụ, sử dụng '==>' với 1.12.3-SNAPSHOT, có vẻ tốt đẹp. Mã kết quả khá dễ đọc, tôi nghĩ vậy. Ngoài ra, đặt tất cả * vals lười * ở trên mã kiểm tra kết quả trong mã đó là một chút dễ dàng hơn để đọc Tôi nghĩ. - Tôi có lẽ sẽ viết lại một chút về bộ thử nghiệm tại một thời điểm, trong vài tháng tới (khi 1.12.3 được phát hành), và xem xét sử dụng '==>'. (Tôi cũng sẽ chia nó thành nhiều bộ thử nghiệm nhỏ hơn và 'include' chúng.) Cảm ơn – KajMagnus

+0

Cảm ơn ghi chú trên 'step (stepOnFail = true)'. – KajMagnus

+0

Tất cả các thay đổi trong 1.12.3-SNAPSHOT: các lỗi có thể được ném từ các bước Given/When/Then, một bước Given có thể được tạo trực tiếp từ một hàm lấy chuỗi mô tả đầy đủ, bước (stopOnFail = true) hoạt động như mong đợi với một đặc tả tuần tự. – Eric

0

Specs doc tiểu bang mà bạn có thể sử dụng .orSkip để bỏ qua phần còn lại của ví dụ trong trường hợp của một thất bại

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

Nhưng tôi đã không thử nó cá nhân

+0

Thực ra chỉ hủy bỏ các thử nghiệm còn lại trong '{...}' hiện tại khối (được gọi là "mảnh thử nghiệm *"). Những gì tôi đang tìm kiếm, là cái gì đó giết chết tất cả các mảnh thử nghiệm tiếp theo ở cùng độ sâu (ý tôi là, "chiều sâu" trong đó văn bản "Ví dụ thứ hai ..." bắt đầu, không phải là "chiều sâu" bên trong '{ ...} 'khối). Tôi sẽ cập nhật câu hỏi của mình để làm cho điều này rõ ràng hơn. – KajMagnus

1

(Liên quan đến câu hỏi 1: Tôi không biết nếu có một số thay thế tốt hơn cho các var s bên trong các ví dụ. Có lẽ các ví dụ của tôi chỉ đơn giản là quá dài, và có lẽ tôi nên chia Spec của tôi: s thành nhiều thông số kỹ thuật nhỏ hơn.)

Về câu hỏi 2, tôi thấy in this email by etorreborre rằng ngăn chặn các xét nghiệm tiếp theo có thể được thực hiện như sau:

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

"ex4" >> ok 

(EX4 sẽ bị bỏ qua nếu EX1, EX2 hoặc ex3 thất bại. (Điều này không làm việc như mong đợi trong Specs2 < 1.12.3 nếu bạn đang sử dụng một spec tuần tự, tuy nhiên.))


Dưới đây là một cách khác: Theo this Specs2 Googl groups email by etorreborre ai có thể làm xét nghiệm tiếp theo dừng lại trên thất bại, như vậy: ("example2" sẽ được bỏ qua, nhưng "example3" và "4" sẽ chạy)

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

Trong this email by etorreborre có một phương pháp để hủy bỏ sau thông số kỹ thuật nếu một n dụ thất bại, nếu bạn đã bao gồm một danh sách các thông số kỹ thuật:

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

Và bạn cần phải chỉnh sửa tùy chọn thử nghiệm trong build.sbt nên số kỹ thuật đứa trẻ không được thực hiện một lần nữa indepentendly sau khi họ đã được đưa vào.Từ email:

testOptions := Seq(Tests.Filter(s => 
    Seq("Spec", "Selenium").exists(s.endsWith(_)) && 
    ! s.endsWith("ChildSpec")))