Warum wir Vulcan an Bord brauchen: Spock Framework Review

Die Testautomatisierung hilft dabei, die Qualität eines IT-Produkts ständig zu überwachen und die Kosten langfristig zu senken. In der Automatisierung gibt es verschiedene Ansätze, zum Beispiel Behavior Driven Development (BDD), Entwicklung durch Verhalten.



Mit diesem Ansatz sind Gurke, Roboter-Framework, Verhalten und andere verbunden, die die Ausführungsskripte und die Implementierung jedes Konstrukts trennen. Diese Trennung hilft beim Schreiben lesbarer Skripte, ist jedoch zeitaufwändig und kann daher beim Schreiben einer Implementierung unpraktisch sein.



Lassen Sie uns einen Blick darauf werfen, wie Sie Ihre Arbeit mit BDD mit den richtigen Tools vereinfachen können - zum Beispiel mit dem Spock-Framework, das die Schönheit, den Komfort von BDD-Prinzipien und die Funktionen von jUnit kombiniert.







Spock Framework



Spock ist ein Test- und Spezifikationsframework für Java- und Groovy-Anwendungen. Auf der Grundlage der JUnit-Plattform ist dieses Framework mit allen gängigen IDEs (insbesondere IntelliJ IDEA), verschiedenen Build-Tools (Ant, Gradle, Maven) und CI-Servern (Continuous Integration) kompatibel.



Wie man die Entwickler des Frameworks schreibt , hat Spock « JUnit , RSpec , jMock , Mockito , Groovy's , Scala's , Vulcans und andere aufregende Lebensformen inspiriert ."



In diesem Artikel werfen wir einen Blick auf die neueste verfügbare Version, Spock Framework 2.0. Seine Funktionen: die Fähigkeit, JUnit5, Java 8+, groovy 2.5 zu verwenden (es gibt auch eine Assembly mit Version 3.0). Spock ist unter Apache 2.0 lizenziert und verfügt über eine reaktionsschnelle Benutzergemeinschaft. Die Framework-Entwickler verfeinern und entwickeln Spock weiter, das bereits viele Erweiterungen enthält, mit denen Sie Ihren Testlauf optimieren können. Einer der interessantesten angekündigten Verbesserungsbereiche ist beispielsweise die Hinzufügung einer parallelen Testausführung.



Groovy



Groovy ist eine objektorientierte Programmiersprache, die für die Java-Plattform als Add-On mit Python-, Ruby- und Smalltalk-Funktionen entwickelt wurde. Groovy verwendet eine Java-ähnliche Syntax mit dynamischer Kompilierung zu JVM-Bytecode und arbeitet direkt mit anderem Java-Code und Bibliotheken. Die Sprache kann in jedem Java-Projekt oder als Skriptsprache verwendet werden.



Zu den Merkmalen von Groovy gehören: sowohl statische als auch dynamische Eingabe; integrierte Syntax für Listen, Arrays und reguläre Ausdrücke; Überladevorgänge. Schließungen in Groovy erschienen jedoch lange vor Java.



Groovy eignet sich gut für die schnelle Testentwicklung, bei der Sie pythonähnlichen syntaktischen Zucker verwenden können, ohne sich um die Eingabe von Objekten kümmern zu müssen.



Funktionen von Spock Framework



Eines der Hauptmerkmale des Frameworks ist, dass der Entwickler in der Lage ist, Spezifikationen mit den erwarteten Systemmerkmalen unter Verwendung der Prinzipien des BDD-Ansatzes zu schreiben . Dieser Ansatz ermöglicht es, geschäftsorientierte Funktionstests für Softwareprodukte mit hoher thematischer und organisatorischer Komplexität zu erstellen.



Die Spezifikation ist eine groovige Klasse, die spock.lang.Specification erweitert



class MyFirstSpecification extends Specification {
  // fields
  // fixture methods
  // feature methods
  // helper methods
}


Die Stückliste kann verschiedene Hilfsfelder enthalten, die für jede Stücklistenklasse ausgelöst werden.



Mit der Annotation @Shared können Sie Klassen, die von der Spezifikation geerbt wurden, Zugriff auf das Feld gewähren.



abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver


    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }

}


Anpassungsmethoden für Stücklistenklassen:



def setupSpec() {} //     feature    
def setup() {}     //    feature 
def cleanup() {}   //    feature 
def cleanupSpec() {} //     feature   


Die folgende Tabelle zeigt, welche Schlüsselwörter und Methoden im Spock-Framework JUnit-Gegenstücke haben.







Teigblöcke



In Spock Framework ist jede Testphase in einen separaten Codeblock unterteilt (ein Beispiel finden Sie in der Dokumentation ).







Ein Codeblock beginnt mit einer Beschriftung und endet mit dem Beginn des nächsten Codeblocks oder dem Ende eines Tests.



Der angegebene Block ist für die Einstellung der anfänglichen Testbedingungen verantwortlich. Wenn , dann werden



Blöcke immer zusammen verwendet. Der when- Block enthält das Stimulans, den Stimulus des Systems, und der then- Block enthält die Antwort des Systems. In Fällen, in denen es möglich ist, die when-then-Klausel auf einen einzelnen Ausdruck zu verkürzen , können Sie einen einzelnen Expect- Block verwenden



... Die folgenden Beispiele werden aus der offiziellen Spock-Framework-Dokumentation verwendet:



when:
def x = Math.max(1, 2)
 
then:
x == 2


oder ein Ausdruck



expect:
Math.max(1, 2) == 2


Der Bereinigungsblock wird verwendet, um Ressourcen vor der nächsten Iteration des Tests freizugeben.



given:
def file = new File("/some/path")
file.createNewFile()
 
// ...
 
cleanup:
file.delete()


Die where-Klausel wird verwendet, um Daten zum Testen zu übertragen (Data Driven Testing).



def "computing the maximum of two numbers"() {
  expect:
  Math.max(a, b) == c
 
  where:
  a << [5, 3]
  b << [1, 9]
  c << [5, 9]
}


Die Arten der Eingabedatenübertragung werden unten diskutiert.



Beispiel für eine Testimplementierung in Spock Framework



Als nächstes werden Ansätze zur Implementierung des Testens der Webseite auf Benutzerautorisierung im System unter Verwendung von Selen betrachtet.



import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification

abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver
    
    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }
}


Hier sehen wir die Basisklasse der Seitenspezifikation. Zu Beginn der Klasse sehen wir den Import der erforderlichen Klassen. Das Folgende ist die gemeinsam genutzte Annotation , mit der abgeleitete Klassen auf den Webtreiber zugreifen können. Im Block setup () sehen wir den Code zum Initialisieren des Webtreibers und zum Öffnen der Webseite. Der Block cleanup () enthält den Code zum Beenden des Webtreibers.



Als nächstes gehen wir zu einer Übersicht über die Spezifikation der Benutzeranmeldeseite über.



import pages.LoginPage
import spock.lang.Issue

class LoginPageTest extends PagesBaseSpec {

    @Issue("QAA-1")
    def "QAA-1: Authorization with correct login and password"() {

        given: "Login page"
        def loginPage = new LoginPage(driver)

        and: "Correct login and password"
        def adminLogin = "adminLogin"
        def adminPassword = "adminPassword"

        when: "Log in with correct login and password"
        loginPage.login(adminLogin, adminPassword)

        then: "Authorized and moved to main page"
        driver.currentUrl == "www.anywebservice.ru/main"
    }
}


Die Autorisierungsseiten-Spezifikation erbt von der Basisseiten-Spezifikation. Die Ausgabe Annotation spezifiziert die ID des Tests in einem externen Verfolgungssystem (wie Jira). In der nächsten Zeile sehen wir den Namen des Tests, der gemäß Konvention durch Zeichenfolgenliterale festgelegt wird, sodass Sie beliebige Zeichen im Namen des Tests verwenden können (einschließlich russischer Zeichen). In dem angegebenen Block wird das Seitenobjekt der Autorisierungsseitenklasse initialisiert und das richtige Login und Passwort für die Autorisierung im System erhalten. Im Wann- Block wird die Autorisierungsaktion ausgeführt. Der then- Block wird verwendet, um die erwartete Aktion zu überprüfen, nämlich die erfolgreiche Autorisierung und Umleitung zur Hauptseite des Systems.



Am Beispiel dieser Spezifikation sehen wir das bedeutendste Plus der Verwendung des BDD-Paradigmas in Spock - die Systemspezifikation ist gleichzeitig ihre Dokumentation. Jeder Test beschreibt ein bestimmtes Verhalten, jeder Schritt im Test hat eine eigene Beschreibung, die nicht nur für Entwickler, sondern auch für Kunden verständlich ist. Beschreibungen von Blöcken können nicht nur im Quellcode des Tests, sondern auch in Diagnosemeldungen oder Berichten über den Testvorgang dargestellt werden.



Das Framework bietet die Möglichkeit, verschiedene Anmeldungen und Kennwörter zum Testen zu übertragen (Test parametrisieren).



Datengesteuertes Testen im Spock Framework



Datengesteuertes Testen = tabellengesteuertes Testen = parametrisiertes Testen


Um ein Szenario mit mehreren Parametern zu testen, können Sie verschiedene Optionen zum Übergeben verwenden.



Datentabellen



Schauen wir uns einige Beispiele aus der offiziellen Rahmendokumentation an.



class MathSpec extends Specification {
  def "maximum of two numbers"() {
    expect:
    Math.max(a, b) == c
 
    where:
    a | b | c
    1 | 3 | 3
    7 | 4 | 7
    0 | 0 | 0
  }
}


Jede Zeile in der Tabelle ist eine separate Testiteration. Die Tabelle kann auch durch eine Spalte dargestellt werden.



where:
a | _
1 | _
7 | _
0 | _


_ Ist ein Stub-Objekt der Stücklistenklasse.



Zur besseren visuellen Wahrnehmung der Parameter können Sie das obige Beispiel in der folgenden Form umschreiben:



def "maximum of two numbers"() {
    expect:
    Math.max(a, b) == c
 
    where:
    a | b || c
    1 | 3 || 3
    7 | 4 || 7
    0 | 0 || 0
}


Jetzt können wir sehen, dass a , b Eingaben sind und c der erwartete Wert ist.



Datenleitungen



In einigen Fällen ist die Verwendung der Designtabelle sehr umständlich. In solchen Fällen können Sie die folgende Art der Parameterübergabe verwenden:



...
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]


Hier ist die Linksverschiebung << ein überladener Groovy-Operator, der jetzt zum Hinzufügen von Elementen zur Liste dient.



Für jede Testiteration werden die folgenden Daten aus der Liste für jede Variable angefordert:



1 Iteration: a = 1, b = 3, c = 3;

2. Iteration: a = 7, b = 4, c = 7;

3 Iteration: a = 0, b = 0, c = 0.



Darüber hinaus können die Eingabedaten nicht nur explizit übertragen, sondern bei Bedarf auch aus verschiedenen Quellen angefordert werden. Zum Beispiel aus der Datenbank:



@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
 
def "maximum of two numbers"() {
  expect:
  Math.max(a, b) == c
 
  where:
  [a, b, c] << sql.rows("select a, b, c from maxdata")
}


Zuweisung von Datenvariablen



...
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b


Hier sehen wir die dynamisch berechnete Variable c in den Testdaten.



Kombination verschiedener Arten der Parameterübertragung



...
where:
a | _
3 | _
7 | _
0 | _
 
b << [5, 0, 0]
 
c = a > b ? a : b


Niemand verbietet Ihnen, bei Bedarf mehrere Übertragungsarten gleichzeitig zu verwenden.



Ein Beispiel für die Implementierung eines parametrisierten Tests im Spock Framework



@Issue("QAA-1-parametrized")
def "QAA-1-parametrized: Authorization with correct login and password"() {

   given: "Login page"
   def loginPage = new LoginPage(driver)

   when: "Log in with correct login and password"
   loginPage.login(login, password)

   then: "Authorized and moved to main page"
   driver.currentUrl =="www.anywebservice.ru/main"

   where: "Check for different logins and passwords"
   login            | password
   "adminLogin"     | "adminPassword"
   "moderatorLogin" | "moderatorPassword"
   "userLogin"      | "userPassword"
}


Hier sehen wir den bereits bekannten where-Block, in dem die Schlüssel der Parameter (Logins und Passwörter) gesetzt sind, die in der Konfigurationsdatei gespeichert sind.



Aufgrund der Besonderheiten der verwendeten Implementierung der Spezifikation werden der Webtreiber-Konfigurationszyklus und sein Schließen (und damit das Schließen des Browsers) für jeden Parameter durchgeführt, was sich negativ auf die Testausführungszeit auswirkt. Wir empfehlen, die Spezifikation fertigzustellen und die Testlaufzeit zu verbessern.



Ein Beispiel für die Implementierung eines parametrisierten Tests mit einer modifizierten Spezifikation



Vor der Überarbeitung



abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver


    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }

}


Nach der Überarbeitung



import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification

abstract class PagesNoRestartBaseSpec extends Specification {

    @Shared
    protected WebDriver driver

    def setupSpec() {
        this.driver = DriverFactory.createDriver()
    }

    def setup() {
        this.driver.get("www.anywebservice.ru")
    }

    def cleanup() {
        this.driver.get("www.anywebservice.ru/logout")
        this.driver.manage().deleteAllCookies();
    }

    void cleanupSpec() {
        this.driver.quit()
    }
}


In der aktualisierten Spezifikation sehen wir, dass das Verfahren zum Erstellen eines Webtreibers nur ausgeführt wird, wenn die Spezifikationsklasse eingerichtet und der Browser erst geschlossen wird, nachdem die Tests aus der Spezifikation ausgeführt wurden. In der setup () -Methode sehen wir denselben Code zum Abrufen der Webadresse des Dienstes und zum Öffnen im Browser. In der cleanup () -Methode gehen wir zu www.anywebservice.ru/logout , um die Arbeit mit dem Dienst für den aktuellen Benutzer zu beenden und Cookies zu löschen (Zum Testen des aktuellen Webdienstes reicht dieses Verfahren aus, um einen "eindeutigen" Start zu simulieren.) Der Testcode selbst hat sich nicht geändert.



Infolgedessen konnten wir mithilfe einfacher Verbesserungen die Autotest-Betriebszeit im Vergleich zur ursprünglichen Implementierung um mindestens das Doppelte verkürzen.



Vergleich der Tests für testNG, pytest, pytest-bdd



Zunächst werden wir uns die Implementierung eines Tests auf dem testNG-Testframework in der Java-Programmiersprache ansehen, der wie das Spock Framework vom jUnit-Framework inspiriert ist und datengesteuertes Testen unterstützt.



package javaTests;

import org.testng.Assert;
import org.testng.annotations.*;
import pages.LoginPage;


public class LoginPageTest extends BaseTest {


    @BeforeClass
    public final void setup() {
        createDriver();
        driver.get("www.anywebservice.ru");
    }

    @DataProvider(name = "userParameters")
    public final Object[][] getUserData(){
        return new Object[][] {
                {"adminLogin", "adminPassword"},
                {"moderatorLogin", "moderatorPassword"},
                {"userLogin", "userPassword"}
        };
    }

    @Test(description = "QAA-1-1: Authorization with correct login and password",
            dataProvider = "userParameters")
    public final void authorizationWithCorrectLoginAndPassword(String login, String password){
        //Login page
        LoginPage loginPage = new LoginPage(driver);

        //Log in with correct login and password
        loginPage.login(login, password);

        //Authorized and moved to main page
        Assert.assertEquals("www.anywebservice.ru/main", driver.getCurrentUrl());
    }

    @AfterMethod
    public final void cleanup() {
        driver.get("www.anywebservice.ru/logout");
        driver.manage().deleteAllCookies();
    }

    @AfterClass
    public final void tearDown() {
        driver.quit();
    }
}


Hier sehen wir die Testklasse mit allen erforderlichen setup () -, cleanup () -Methoden sowie die Parametrisierung des Tests in Form einer zusätzlichen getUserData () -Methode mit der Annotation @DataProvider, die nach dem, was wir im Test mit Spock untersucht haben, etwas umständlich aussieht Rahmen. Um zu verstehen, was im Test passiert, wurden Kommentare hinterlassen, die der Beschreibung der Schritte ähneln.



Es ist zu beachten, dass testNG im Gegensatz zu Spock Framework die parallele Testausführung unterstützt.







Als nächstes gehen wir zu einem Test über, der das Pytest-Test-Framework in der Programmiersprache Python verwendet.



import pytest
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from PageObjects.LoginPage import LoginPage


class TestLogin(object):

    @pytest.mark.parametrize("login,password", [
        pytest.param(("adminLogin", "adminPassword"), id='admin'),
        pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
        pytest.param(("userLogin", "userPassword"), id='user')
    ])
    def test_authorization_with_correct_login_and_password(self, login, password, driver, test_cleanup):
        # Login page
        login_page = LoginPage(driver)
        # Log in with correct login and password
        login_page.login(login, password)

        # Authorized and moved to main page
        assert expected_conditions.url_to_be("www.anywebservice.ru/main")
 
    @pytest.fixture()
    def test_cleanup(self, driver):
        yield "test"
        driver.get("www.anywebservice.ru/logout")
        driver.delete_all_cookies()


Hier sehen wir auch die Unterstützung für datengesteuertes Testen als separates Konstrukt, ähnlich wie @DataProvider in testNG. Die Methode zum Konfigurieren des Webtreibers ist im Treibergerät "versteckt". Dank dynamischer Typisierung und Pytest-Fixtures sieht der Code für diesen Test sauberer aus als Java.







Als nächstes gehen wir mit dem pytest-bdd-Plugin zu einer Übersicht über den Testcode über, mit der Sie Tests in Form von Gherkin-Feature-Dateien schreiben können (reiner BDD-Ansatz).



login.feature



Feature: Login page
  A authorization

  Scenario: Authorizations with different users
    Given Login page
    When Log in with correct login and password
    Then Authorized and moved to main page


test_login.py



import pytest
from pytest_bdd import scenario, given, when, then
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from PageObjects.LoginPage import LoginPage


@pytest.mark.parametrize("login,password", [
    pytest.param(("adminLogin", "adminPassword"), id='admin'),
    pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
    pytest.param(("userLogin", "userPassword"), id='user')
])
@scenario('login.feature', 'Authorizations with different users')
def test_login(login, password):
    pass


@given('Login page')
def login_page(driver):
    return LoginPage(driver)


@when('Log in with correct login and password')
def login_with_correct_login_and_password(login_page, login, password):
    login_page_object = login_page
    login_page_object.login(login, password)

@then('Authorized and moved to main page')
def authorized_and_moved_to_main_page(driver, login):
    assert expected_conditions.url_to_be("www.anywebservice.ru/main")


Einer der Vorteile ist, dass es sich immer noch um ein Pytest-Framework handelt, das viele Plugins für verschiedene Situationen enthält, einschließlich zum parallelen Ausführen von Tests. Der Nachteil ist der reine BDD-Ansatz selbst, der den Entwickler ständig mit seinen eigenen Eigenschaften einschränkt. Mit Spock Framework können Sie im Vergleich zum PyTest + pytest-bdd-Bundle präziseren und einfach zu gestaltenden Code schreiben.







Fazit



In diesem Artikel haben wir uns mit der Vereinfachung der Arbeit mit BDD mithilfe des Spock-Frameworks befasst. Lassen Sie uns zusammenfassend kurz die wichtigsten Vor- und Nachteile von Spock im Vergleich zu einigen anderen gängigen Test-Frameworks hervorheben.



Vorteile:



  • Die Verwendung von BDD-Prinzipien anstelle eines reinen BDD-Ansatzes bietet Ihnen mehr Flexibilität beim Schreiben von Tests.
  • Die schriftliche Testspezifikation ist auch die Dokumentation des Systems.
  • .
  • groovy ( , , closures ).


:



  • groovy. , , IDE , . Intellij IDEA, , , , .
  • groovy JVM -. , groovy, , . java, groovy .
  • Der Erweiterungssatz ist beispielsweise nicht so umfangreich wie der von testNG. Infolgedessen gibt es keine parallele Testreihe. Es ist geplant, diese Funktionalität hinzuzufügen, aber der Zeitpunkt ihrer Implementierung ist unbekannt.


Letztendlich verdient das Spock Framework zweifellos Aufmerksamkeit, da es zur Lösung des komplexen allgemeinen Problems der Automatisierung von Geschäftsszenarien für Softwareprodukte mit hoher thematischer und organisatorischer Komplexität geeignet ist.



Was können Sie noch lesen:






All Articles