2012-11-01 10 views
5

Czy istnieje jakiś preferowany sposób zaprojektowania testu Specs2, z wieloma testami, które zależą od wyników poprzednich testów?W jaki sposób zaprojektować test bazy danych Specs2, z niezależnymi testami?

Poniżej znajduje się mój obecny zestaw testów. Nie podoba mi się var s pomiędzy fragmentami testowymi. Są jednak "potrzebne", ponieważ niektóre testy generują numery identyfikacyjne, które ponownie wykorzystują testy.

  1. Czy powinienem raczej zapisać numery identyfikacyjne w kontekście Specs2, czy utworzyć oddzielny obiekt, który przechowuje wszystkie zmienne ustawienia? I umieść tylko fragmenty testowe w obiekcie specyfikacji? A może jest jeszcze lepsze podejście?

  2. Jeśli test się nie powiedzie, chciałbym anulować pozostały test na tej samej głębokości. Czy mogę sprawić, aby fragmenty testowe były od siebie zależne? (Wiem, że mogę anulować pozostałe dopasowujących w jednym fragmencie testowego (przy użyciu testów zmienny lub poprzez orSkip), ale to, co o anulowanie całych fragmentów?)

.

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

Specyfikacja BTW specs2 3.x została zaprojektowana w celu rozwiązania tego problemu, w którym można tworzyć dowolne testy opierające się na wynikach poprzednich testów. Zobacz tutaj: https://etorreborre.github.io/specs2/guide/SPECS2-3.1.1/org.specs2.guide.CreateOnlineSpecifications.html – Eric

Odpowiedz

4

Istnieją 2 części do Twojego pytania: wykorzystujące vars do przechowywania stan pośredni, a zatrzymując się przykłady, gdy jedna jest wadliwa.

1 - Korzystanie Vars

Nie

są pewne alternatywy użyciu vars przy użyciu zmienny specyfikacji.

Można użyć lazy vals przedstawiający etapy procesu:

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

Ponieważ wartości te są leniwi, zostanie wywołana tylko gdy przykłady są wykonywane.

Jeśli jesteś gotowy, aby zmienić strukturę aktualnej specyfikacji można również użyć najnowszej 1.12.3-SNAPSHOT i Grupy te wszystkie małe oczekiwania w jeden przykład:

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

Jeśli którykolwiek z tych oczekiwań nie to reszta nie zostanie wykonany, a otrzymasz wiadomość awaryjności jak:

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) 

innej możliwości, co wymagałoby 2 małych ulepszeń, byłoby użyć Given/When/Then sposób specyfikacji pisaniu, ale za pomocą „rzucane” expectati kroki wewnątrz Given i When kroków. Jak widać w tym podręczniku użytkownika, Given/When/Then kroki wyodrębnić dane z ciągów i przekazać informacje wpisane do następnej 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 
    } 
} 

Ulepszenia, które mam zamiar zrobić, to:

  • wyjątki niepowodzenia catch, aby zgłosić je jako awarie, a nie błędy
  • usunąć konieczność użycia groupAs(".*") and, gdy nie ma nic do wyodrębnienia z opisu ciągu.

W takim przypadku powinno wystarczyć, aby napisać:

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

Inną możliwością byłoby pozwalają bezpośrednio napisać:

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

gdzie Given[T] obiekt może być utworzony z String => MatchResult[T] ponieważ obiekt MatchResult[T] zawiera już wartość typu T, która stanie się "Given".

2 - Zatrzymaj wykonanie po upadającego przykład

Korzystanie niejawny WhenFailAround kontekst jest z pewnością najlepszym sposobem, aby robić to, co chcesz (chyba, że ​​chcesz iść z oczekiwaniami opisów, jak pokazano powyżej G/W/T przykład).

Uwaga na step(stepOnFail = true)

The step(stepOnFail = true) twórczości przerywając następujące przykłady, jeśli jeden z przykładów w poprzednim bloku jednoczesnych przykładów powiodło się. Jednak, gdy używasz sequential, poprzedni blok jest ograniczony tylko do jednego przykładu. Stąd to, co widzisz. Właściwie uważam, że jest to błąd i wszystkie pozostałe przykłady nie powinny być wykonywane, niezależnie od tego, czy używasz sekwencyjnego, czy też nie. Bądź więc czujny, by rozwiązać ten tydzień.

+0

Zgrupowanie oczekiwań w przykładzie za pomocą '==>' z 1.12.3-SNAPSHOT, wydaje się fajne. Wynikowy kod jest dość łatwy do odczytania, jak sądzę. Również umieszczenie wszystkich * leniwych vals * powyżej kodu testowego daje w efekcie kod, który jest nieco łatwiejszy do odczytania. - Prawdopodobnie przepiszę trochę zestawu testowego naraz, w ciągu kilku następnych miesięcy (po wydaniu wersji 1.12.3) i rozważę użycie '==>'. (Podzielę to również na wiele mniejszych pakietów testowych i "uwzględnię" je.) Dzięki – KajMagnus

+0

Dzięki za uwagę na 'step (stepOnFail = true)'. – KajMagnus

+0

Wszystkie zmiany są w 1.12.3-SNAPSHOT: można teraz zgłaszać awarie z kroków Given/When/Then, a dany krok można bezpośrednio utworzyć z funkcji pobierającej pełny ciąg znaków, krok (stopOnFail = true) zachowuje się zgodnie z oczekiwaniami ze specyfikacją sekwencyjną. – Eric

0

Specs doc stany, które można użyć .orSkip do pominąć resztę przykład w przypadku awarii

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

Ale nie próbowałem tego osobiście

+0

W rzeczywistości tylko anuluje pozostałe testy w bieżącym '{...}' blok (który nazywany jest "* fragmentem testowym *"). To, czego szukam, jest czymś, co zabija wszystkie kolejne fragmenty testowe na tej samej głębokości (mam na myśli "głębokość", gdzie zaczyna się tekst "Drugi przykład ...", a nie "głębia" wewnątrz '{} ...} 'blok). Zaktualizuję moje pytanie, aby było to bardziej jasne. – KajMagnus

1

(dotyczy pytanie 1: Nie wiem, czy w przykładach jest jakaś lepsza alternatywa dla var. Być może moje przykłady są po prostu zbyt długie i być może powinienem podzielić moje Spec: s na wiele mniejszych specyfikacji.)

Odnośnie do pytania 2, znalazłem in this email by etorreborre że zatrzymanie kolejnych testów można zrobić tak:

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

"ex4" >> ok 

(Ex4 będą pomijane, jeśli EX1, EX3 EX2 lub nie. (To nie działa zgodnie z oczekiwaniami w Specs2 < 1.12.3 jeśli używasz sekwencyjną specyfikację, jednak.))


Oto kolejny sposób: Według this Specs2 Googl groups email by etorreborre jeden może mieć kolejne testy zatrzymać na niepowodzenie, tak: („przyklad2” zostanie pominięty, ale „Przykład3” i „4” będzie działać)

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

W this email by etorreborre istnieje metoda, aby anulować mu późniejsze Specyfikacja jeśli n przykład nie powiedzie się, jeśli masz zawierać wykaz specyfikacji:

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

I trzeba by zmieniać opcje testowe w build.sbt tak specyfikacje dziecko nie są wykonywane ponownie indepentendly po tym jak zostały uwzględnione.Z e-mail:

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