2012-11-01 12 views
5

Esiste un modo preferibile per progettare un test Specs2, con molti test che dipendono dai risultati dei test precedenti?Come si progetta un test del database Specs2, con test interdipendenti?

Di seguito, troverete la mia attuale suite di test. Non mi piace il s tra i frammenti di prova. Tuttavia, sono "necessari", dal momento che alcuni test generano numeri ID che i successivi test riutilizza.

  1. Devo forse memorizzare i numeri ID in un contesto Specs2, oppure creare un oggetto separato che contenga tutti gli stati mutabili? E inserire solo i frammenti di prova nell'oggetto della specifica? O c'è un approccio ancora migliore?

  2. Se un test fallisce, vorrei annullare il test rimanente alla stessa profondità. Posso fare in modo che i frammenti del test dipendano l'uno dall'altro? (So ​​che posso cancellare matchers rimanenti in un singolo frammento di prova (mediante i test mutevoli, o tramite orSkip), ma per quanto riguarda l'annullamento frammenti interi?)

.

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

specifiche BTW 2.x è stato progettato per risolvere questo problema esatto, in cui è possibile creare test arbitrari basandosi sui risultati dei test precedenti. Vedi qui: https://etorreborre.github.io/specs2/guide/SPECS2-3.1.1/org.specs2.guide.CreateOnlineSpecifications.html – Eric

risposta

4

Ci sono 2 parti alla tua domanda: utilizzando Vars per la memorizzazione di stato intermedio, e l'arresto esempi quando uno sta fallendo.

1 - Utilizzo Vars

Ci

sono alcune alternative all'utilizzo Vars quando si utilizza una specifica mutevole.

È possibile utilizzare lazy vals che rappresenta le fasi del processo:

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

Tali valori sono pigri, perché saranno chiamati solo quando vengono eseguiti gli esempi.

Se si è pronti a cambiare la struttura della vostra specifica corrente è anche possibile utilizzare l'ultima 1.12.3-SNAPSHOT e di gruppo tutte quelle piccole aspettative in un esempio:

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

Se uno qualsiasi di tali aspettative fallisce, allora non verrà eseguito il resto e si otterrà un messaggio di errore del tipo:

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) 

Un'altra possibilità, che richiederebbe 2 piccoli miglioramenti, sarebbe quella di utilizzare il modo Given/When/Then delle specifiche di scrittura ma utilizzando expectati "gettato" passaggi all'interno dei passi Given e When. Come si può vedere nella Guida per l'utente, i Given/When/Then passi estrarre i dati da stringhe e passano le informazioni digitate al successivo 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 
    } 
} 

I miglioramenti, che ho intenzione di fare, sono:

  • rilevare le eccezioni di errore per segnalarle come errori e non errori
  • rimuovere la necessità di utilizzare groupAs(".*") and quando non c'è nulla da estrarre dalla descrizione della stringa.

In tal caso dovrebbe essere sufficiente scrivere:

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

Un'altra possibilità sarebbe quella di permettere di scrivere direttamente:

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

in cui un oggetto Given[T] può essere creato da un String => MatchResult[T] perché l'oggetto MatchResult[T] contiene già un valore di tipo T, che diventerebbe un "Dato".

2 - Fermare l'esecuzione dopo un esempio di mancanza di

Uso del implicita WhenFailAround contesto è certamente il modo migliore per fare ciò che si vuole (a meno che non si va con le aspettative descrizioni come indicato sopra la G/W/T esempio).

Note sulla step(stepOnFail = true)

I step(stepOnFail = true) opere interrompendo i seguenti esempi se un esempio nel blocco precedente di esempi simultanee fallito. Tuttavia, quando si utilizza sequential, quel blocco precedente è limitato a un solo esempio. Quindi cosa stai vedendo. In realtà penso che questo sia un bug e che tutti gli esempi rimanenti non debbano essere eseguiti, sia che tu stia usando sequenziale o meno. Quindi rimanete sintonizzati per una soluzione in arrivo questo fine settimana.

+0

Raggruppare le aspettative in un esempio, usando '==>' con 1.12.3-SNAPSHOT, sembra bello. Il codice risultante è abbastanza facile da leggere, penso. Inoltre, posizionando tutti i * lazy vals * sopra il codice del test si ottiene un codice un po 'più facile da leggere, penso. - Probabilmente riscriverò un po 'della suite di test alla volta, nei prossimi mesi (quando è stato rilasciato 1.12.3), e prendere in considerazione l'uso di '==>'. (Lo dividerò anche in molti piccoli gruppi di test e li includerò.) Grazie – KajMagnus

+0

Grazie per la nota su 'step (stepOnFail = true)'. – KajMagnus

+0

Tutte le modifiche sono in 1.12.3-SNAPSHOT: gli errori possono ora essere generati dai passi Given/When/Then, un passo Given può essere creato direttamente da una funzione che utilizza la stringa di descrizione completa, il passo (stopOnFail = true) si comporta come previsto con una specifica sequenziale. – Eric

0

Specs doc stati che si possono utilizzare per .orSkip saltare il resto della esempio in caso di un fallimento

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

ma non l'ho provato personalmente

+0

In realtà questo annulla solo i test rimanenti nella corrente '{...}' block (che è chiamato "* test framment *" penso). Quello che sto cercando è qualcosa che uccide tutti i successivi frammenti di test alla stessa profondità (intendo, la "profondità" in cui inizia il testo "Il secondo esempio ...", non la "profondità" all'interno di {{ ...} 'blocco). Aggiornerò la mia domanda per renderlo più chiaro. – KajMagnus

1

(questione relativa 1: Non so se ci sia un'alternativa migliore agli esempi . Forse i miei esempi sono semplicemente troppo lunghi, e forse dovrei dividere la mia specifica: s in molte specifiche più piccole.)

riguardante domanda 2, ho trovato che in this email by etorreborre arresto successivi test può essere fatto in questo modo:

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

"ex4" >> ok 

(Ex4 verrà ignorato se EX1, EX2 o ex3 fallisce. (Questo non funziona come previsto in Specs2 < 1.12.3 se si sta utilizzando una specifica sequenza, tuttavia.))


Ecco un altro modo: Secondo this Specs2 Googl groups email by etorreborre può avere le prove successive si fermano in caso di fallimento, in questo modo: ("example2" sarebbe saltato, ma "Esempio3" e "4" si porrebbe)

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

In this email by etorreborre c'è un metodo per annullare le successive specifiche se un n ad esempio non riesce, se hai includere un elenco di specifiche:

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

E avresti bisogno di modificare le opzioni di prova in build.sbt così le specifiche dei bambini non vengono eseguite di nuovo indepentendly dopo che sono stati compresi.Dalla e-mail:

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