Java >> Java Tutorial >  >> Java

Unit-Tests mit Spock Framework schreiben:Einführung in die Spezifikationen, Teil Eins

Wenn wir Unit-Tests mit Spock Framework schreiben, müssen wir sogenannte Spezifikationen erstellen, die die Funktionen unserer Anwendung beschreiben.

Dieser Blogbeitrag bietet eine Einführung in die Spock-Spezifikationen und hilft uns, unsere erste Spezifikation zu erstellen und ihre Struktur zu verstehen.

Beginnen wir mit der Erstellung unserer ersten Spock-Spezifikation.

Erstellen einer Spock-Spezifikation

Wir können eine Spock-Spezifikation erstellen, indem wir eine Groovy-Klasse erstellen, die die spock.lang.Specification erweitert Klasse. Da wir unsere Gradle- und Maven-Projekte so konfiguriert haben, dass sie Spock-Tests ausführen, die von Klassen gefunden wurden, deren Namen mit dem Suffix „Spec“ enden, müssen wir den Namen unserer Spock-Spezifikationsklasse erstellen, indem wir dieser Regel folgen:[Der Name der getestete/spezifizierte Einheit]Spec .

Der Quellcode unserer Spezifikationsklasse sieht wie folgt aus:

import spock.lang.Specification

class ExampleSpec extends Specification {

}

Wir haben gerade unsere erste Spock-Spezifikation erstellt. Leider ist unsere Spezifikation nutzlos, weil sie nichts bewirkt. Bevor wir das ändern können, müssen wir uns den Aufbau einer Spock-Spezifikation genauer anschauen.

Die Struktur einer Spock-Spezifikation

Jede Spezifikation kann die folgenden Teile haben:

  • Instanzfelder sind ein guter Ort, um Objekte zu speichern, die zum Fixture der Spezifikation gehören (d. h. wir verwenden sie, wenn wir unsere Tests schreiben). Außerdem empfiehlt Spock, dass wir unsere Instanzfelder initialisieren, wenn wir sie deklarieren.
  • Vorrichtungsmethoden sind dafür verantwortlich, das System unter Spezifikation (SUS) zu konfigurieren, bevor Feature-Methoden aufgerufen werden, und das System unter Spezifikation zu bereinigen, nachdem Feature-Methoden aufgerufen wurden.
  • Funktionsmethoden spezifizieren das erwartete Verhalten des Systems unter Spezifikation.
  • Hilfsmethoden sind Methoden, die von den anderen Methoden der Spezifikationsklasse verwendet werden.

Das folgende Code-Listing veranschaulicht die Struktur unserer Spezifikation:

import spock.lang.Specification
 
class ExampleSpec extends Specification {
	 //Fields
	 //Fixture methods
	 //Feature methods
	 //Helper methods
}

Wir kennen jetzt die grundlegenden Bausteine ​​einer Spock-Spezifikation. Lassen Sie uns weitermachen und einen genaueren Blick auf Instanzfelder werfen.

Hinzufügen von Feldern zu unserer Spezifikation

Das wissen wir bereits

  • Instanzfelder sind ein guter Ort, um Objekte zu speichern, die zum Fixture der Spezifikation gehören.
  • Wir sollten sie initialisieren, wenn wir sie deklarieren.

Allerdings müssen wir eines lernen, bevor wir unserer Spezifikation Felder hinzufügen können. Eine Spezifikation kann zwei Arten von Instanzfeldern haben:

  • Die in "normalen" Instanzfeldern gespeicherten Objekte werden nicht zwischen Feature-Methoden geteilt. Das bedeutet, dass jede Feature-Methode ein eigenes Objekt bekommt. Wir sollten normale Instanzfelder bevorzugen, da sie uns helfen, Feature-Methoden voneinander zu isolieren.
  • Die in "gemeinsam genutzten" Instanzfeldern gespeicherten Objekte werden von Feature-Methoden gemeinsam genutzt. Wir sollten gemeinsam genutzte Felder verwenden, wenn die Erstellung des betreffenden Objekts teuer ist oder wir etwas mit allen Feature-Methoden teilen möchten.

Fügen wir unserer Spezifikation zwei Instanzfelder hinzu. Wir können dies tun, indem wir diesen Schritten folgen:

  1. Fügen Sie ein "normales" Feld hinzu (uniqueObject ) in die ExampleSpec Klasse und initialisiere sie mit einem neuen Objekt .
  2. Fügen Sie ein gemeinsames Feld hinzu (sharedObject ) in die ExampleSpec Klasse und initialisiere sie mit einem neuen Objekt . Markieren Sie das Feld als geteilt, indem Sie es mit @Shared kommentieren Anmerkung.

Der Quellcode unserer Spezifikationsklasse sieht wie folgt aus:

import spock.lang.Shared
import spock.lang.Specification

class ExampleSpec extends Specification {

    def uniqueObject = new Object();
    @Shared sharedObject = new Object();
}

Lassen Sie uns den Unterschied zwischen diesen Feldern demonstrieren, indem wir unserer Spezifikation zwei Feature-Methoden hinzufügen. Diese Feature-Methoden stellen sicher, dass die toLowerCase() und toUpperCase() Methoden des String Klasse funktionieren wie erwartet. Was uns jedoch am meisten interessiert, ist, dass beide Feature-Methoden gespeicherte Objekte in das uniqueObject schreiben und sharedObject Felder zu System.out .

Der Quellcode unserer Spezifikation sieht wie folgt aus:

import spock.lang.Shared
import spock.lang.Specification

class ExampleSpec extends Specification {

    def message = "Hello world!"

    def uniqueObject = new Object();
    @Shared sharedObject = new Object();

    def "first feature method"() {
        println "First feature method"
        println "unique object: " + uniqueObject
        println "shared object: " + sharedObject

        when: "Message is transformed into lowercase"
        message = message.toLowerCase()

        then: "Should transform message into lowercase"
        message == "hello world!"
    }

    def "second feature method"() {
        println "Second feature method"
        println "unique object: " + uniqueObject
        println "shared object: " + sharedObject

        when: "Message is transformed into uppercase"
        message = message.toUpperCase()

        then: "Should transform message into uppercase"
        message == "HELLO WORLD!"
    }
}

Wenn wir unsere Spezifikation ausführen, sollten wir sehen, dass die folgenden Zeilen in System.out geschrieben werden :

First feature method
unique object: java.lang.Object@5bda8e08
shared object: java.lang.Object@3b0090a4
Second feature method
unique object: java.lang.Object@367ffa75
shared object: java.lang.Object@3b0090a4

Mit anderen Worten, wir können Folgendes sehen:

  • Das im normalen Instanzfeld gespeicherte Objekt wird nicht von Feature-Methoden geteilt.
  • Das Objekt, das im gemeinsam genutzten Instanzfeld gespeichert ist, wird von Feature-Methoden gemeinsam genutzt.

Obwohl wir jetzt Felder zu unserer Spezifikation hinzufügen können, können wir keine sinnvollen Komponententests schreiben, da wir nicht wissen, wie wir das System unter der Spezifikation konfigurieren oder bereinigen können. Es ist an der Zeit herauszufinden, wie wir Fixture-Methoden verwenden können.

Fixture-Methoden verwenden

Wenn wir das System unter Spezifikation konfigurieren wollen, bevor Feature-Methoden aufgerufen werden, und/oder das System unter Spezifikation bereinigen wollen, nachdem Feature-Methoden aufgerufen wurden, müssen wir Fixture-Methoden verwenden.

Eine Spock-Spezifikation kann die folgenden Fixture-Methoden haben:

  • Die setupSpec() -Methode wird aufgerufen, bevor die erste Feature-Methode aufgerufen wird.
  • Das setup() -Methode wird vor jeder Feature-Methode aufgerufen.
  • Die Bereinigung() -Methode wird nach jeder Feature-Methode aufgerufen.
  • Die cleanupSpec() Methode wird aufgerufen, nachdem alle Feature-Methoden aufgerufen wurden.

Der Quellcode unserer Spezifikationsklasse, die alle Fixture-Methoden enthält, sieht wie folgt aus:

import spock.lang.Shared
import spock.lang.Specification

class ExampleSpec extends Specification {

    def setup() {
        println "Setup"
    }

    def cleanup() {
        println "Clean up"
    }

    def setupSpec() {
        println "Setup specification"
    }

    def cleanupSpec() {
        println "Clean up specification"
    }
}

Wenn wir unsere Spezifikation ausführen, stellen wir fest, dass die folgenden Zeilen in System.out geschrieben werden :

Setup specification
Clean up specification

Mit anderen Worten, nur die setupSpec() und cleanupSpec() Methoden werden aufgerufen. Der Grund dafür ist, dass unsere Spezifikation keine Feature-Methoden hat. Deshalb das setup() und cleanup() Methoden werden nicht aufgerufen.

Fügen wir unserer Spezifikation zwei Feature-Methoden hinzu. Diese Feature-Methoden stellen sicher, dass die toLowerCase() und toUpperCase() Methoden des String Klasse funktionieren wie erwartet. Außerdem schreiben beide Feature-Methoden einen "Bezeichner" in System.out .

Der Quellcode unserer Spezifikation sieht wie folgt aus:

import spock.lang.Shared
import spock.lang.Specification

class ExampleSpec extends Specification {

    def message = "Hello world!"

    def setup() {
        println "Setup"
    }

    def cleanup() {
        println "Clean up"
    }

    def setupSpec() {
        println "Setup specification"
    }

    def cleanupSpec() {
        println "Clean up specification"
    }

    def "first feature method"() {
        println "First feature method"

        when: "Message is transformed into lowercase"
        message = message.toLowerCase()

        then: "Should transform message into lowercase"
        message == "hello world!"
    }

    def "second feature method"() {
        println "Second feature method"

        when: "Message is transformed into uppercase"
        message = message.toUpperCase()

        then: "Should transform message into uppercase"
        message == "HELLO WORLD!"
    }
}

Wenn wir unsere Spezifikation ausführen, stellen wir fest, dass die folgenden Zeilen in System.out geschrieben werden :

Setup specification
Setup
First feature method
Clean up
Setup
Second feature method
Clean up
Clean up specification

Dies beweist, dass die Fixture-Methoden in der Reihenfolge aufgerufen werden, die am Anfang dieses Abschnitts beschrieben wurde.

Fahren wir fort und fassen zusammen, was wir aus diesem Blog gelernt haben.

Zusammenfassung

Dieser Blogbeitrag hat uns fünf Dinge beigebracht:

  • Jede Spock-Spezifikation muss die spock.lang.Specification erweitern Klasse.
  • Eine Spock-Spezifikation kann Instanzfelder, Fixture-Methoden, Feature-Methoden und Hilfsmethoden haben.
  • Wir sollten normale Instanzfelder bevorzugen, weil sie uns helfen, Feature-Methoden voneinander zu isolieren.
  • Wir sollten gemeinsam genutzte Instanzfelder nur verwenden, wenn die Erstellung des betreffenden Objekts teuer ist oder wir etwas mit allen Feature-Methoden teilen möchten.
  • Wir können das System unter Spezifikation initialisieren und bereinigen, indem wir Fixture-Methoden verwenden.

Der nächste Teil dieses Tutorials bietet eine Einführung in Feature-Methoden, die das Herzstück einer Spezifikationsklasse sind.

P.S. Sie können die Beispielanwendung dieses Blogbeitrags von Github herunterladen.


Java-Tag