Pourquoi nous avons besoin de Vulcan à bord: Examen du cadre Spock

L'automatisation des tests permet de contrôler en permanence la qualité d'un produit informatique et de réduire les coûts à long terme. Il existe différentes approches en automatisation, par exemple, le développement piloté par le comportement (BDD), le développement par le comportement.



Associés à cette approche sont le concombre, le cadre de robot, le comportement et d'autres, qui séparent les scripts d'exécution et la mise en œuvre de chaque construction. Cette séparation aide à écrire des scripts lisibles, mais elle prend du temps et peut donc être peu pratique lors de l'écriture d'une implémentation.



Voyons comment vous pouvez simplifier votre expérience BDD en utilisant les bons outils - par exemple, le framework Spock, qui combine la beauté, la commodité des principes BDD et les fonctionnalités de jUnit.







Cadre Spock



Spock est un cadre de test et de spécification pour les applications Java et Groovy. En utilisant la plate-forme JUnit comme base, ce framework est compatible avec tous les IDE populaires (en particulier IntelliJ IDEA), divers outils de construction (Ant, Gradle, Maven) et les serveurs d'intégration continue (CI).



Comment écrire les développeurs du framework, Spock «a inspiré le JUnit , le RSpec , le jMock , le Mockito , le Groovy , le Scala , les Vulcains et d'autres formes de vie passionnantes».



Dans cet article, nous allons jeter un œil à la dernière version disponible, Spock Framework 2.0. Ses caractéristiques: la possibilité d'utiliser JUnit5, Java 8+, groovy 2.5 (il existe également un assemblage avec la version 3.0). Spock est licencié sous Apache 2.0 et dispose d'une communauté d'utilisateurs réactive. Les développeurs du framework continuent d'affiner et de développer Spock, qui comprend déjà de nombreuses extensions qui vous permettent d'affiner votre test. Par exemple, l'un des domaines d'amélioration les plus intéressants annoncés est l'ajout de l'exécution de tests en parallèle.



Sensationnel



Groovy est un langage de programmation orienté objet développé pour la plate-forme Java en tant que module complémentaire avec les capacités Python, Ruby et Smalltalk. Groovy utilise une syntaxe de type Java avec une compilation dynamique en bytecode JVM et fonctionne directement avec d'autres codes et bibliothèques Java. Le langage peut être utilisé dans n'importe quel projet Java ou comme langage de script.



Les caractéristiques de groovy comprennent: le typage statique et dynamique; syntaxe intégrée pour les listes, les tableaux et les expressions régulières; surcharge des opérations. Cependant, les fermetures à Groovy sont apparues bien avant Java.



Groovy est bien adapté pour le développement de tests rapides où vous pouvez utiliser du sucre syntaxique de type python sans avoir à vous soucier de la saisie d'objets.



Caractéristiques de Spock Framework



L'une des principales caractéristiques du cadre est que le développeur a la capacité d'écrire des spécifications avec les caractéristiques système attendues en utilisant les principes de l' approche BDD. Cette approche permet de composer des tests fonctionnels orientés métier pour des produits logiciels à haute complexité thématique et organisationnelle.



La spécification est une classe groovy qui étend spock.lang.



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


La nomenclature peut contenir divers champs auxiliaires qui sont déclenchés pour chaque classe de nomenclature.



En utilisant l'annotation @Shared, vous pouvez donner accès au champ aux classes héritées de la spécification.



abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver


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

    void cleanup() {
        driver.quit()
    }

}


Méthodes de personnalisation de classe BOM:



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


Le tableau suivant montre quels mots-clés et méthodes du framework Spock ont ​​des équivalents JUnit.







Blocs de pâte



Dans Spock Framework, chaque phase de test est séparée en un bloc de code distinct (voir la documentation pour un exemple ).







Un bloc de code commence par une étiquette et se termine par le début du bloc de code suivant ou la fin d'un test.



Le bloc donné est chargé de définir les conditions de test initiales.



Bloque le quand , le puis toujours utilisé ensemble. Le bloc when contient le stimulant, le stimulus du système, et le bloc then contient la réponse du système.



Dans les cas où il est possible de raccourcir la clause when-then en une seule expression, vous pouvez utiliser un seul bloc expect... Les exemples suivants seront utilisés à partir de la documentation officielle du framework Spock:



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


ou une expression



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


Le bloc de nettoyage est utilisé pour libérer des ressources avant la prochaine itération du test.



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


La clause where est utilisée pour transférer des données à des fins de test (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]
}


Les types de transfert de données d'entrée seront discutés ci-dessous.



Exemple d'implémentation de test sur Spock Framework



Ensuite, nous examinerons des approches pour la mise en œuvre du test de la page Web pour l'autorisation des utilisateurs dans le système utilisant le sélénium.



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


Ici, nous voyons la classe de base de la spécification de page. Au début de la classe, nous voyons l'importation des classes requises. Voici l'annotation partagée , qui permet aux classes héritées d'accéder au pilote Web. Dans le bloc setup () , nous voyons le code pour initialiser le pilote Web et ouvrir la page Web. Le bloc cleanup () contient le code pour terminer le pilote Web.



Ensuite, passons à un aperçu de la spécification de la page de connexion utilisateur.



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


La spécification de la page d'autorisation hérite de la spécification de la page de base. L'annotation Issue spécifie l'ID du test dans un système de suivi externe (tel que Jira). Dans la ligne suivante, nous voyons le nom du test, qui par convention est défini par des chaînes littérales, ce qui vous permet d'utiliser n'importe quel caractère dans le nom du test (y compris les caractères russes). Dans le bloc donné , l'objet de page de la classe de page d'autorisation est initialisé et le login et le mot de passe corrects pour l'autorisation dans le système sont obtenus. Dans le bloc quand , l'action d'autorisation est exécutée. Le bloc then est utilisé pour vérifier l'action attendue, à savoir l'autorisation réussie et la redirection vers la page principale du système.



En utilisant cette spécification comme exemple, nous voyons l'avantage le plus significatif de l'utilisation du paradigme BDD dans spock - la spécification du système est en même temps sa documentation. Chaque test décrit un certain comportement, chaque étape du test a sa propre description, compréhensible non seulement pour les développeurs, mais aussi pour les clients. Les descriptions des blocs peuvent être présentées non seulement dans le code source du test, mais également dans des messages de diagnostic ou des rapports sur l'opération de test.



Le framework offre la possibilité de transférer divers identifiants et mots de passe pour les tests (paramétrer le test).



Test piloté par les données dans Spock Framework



Test piloté par les données = test piloté par table = test paramétré


Pour tester un scénario avec plusieurs paramètres, vous pouvez utiliser diverses options pour les transmettre.



Tableaux de données



Regardons quelques exemples de la documentation officielle du cadre.



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


Chaque ligne du tableau est une itération de test distincte. En outre, le tableau peut être représenté par une colonne.



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


_ Est un objet stub de la classe BOM.



Pour une meilleure perception visuelle des paramètres, vous pouvez réécrire l'exemple ci-dessus sous la forme suivante:



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


Nous pouvons maintenant voir que a , b sont des entrées et c est la valeur attendue.



Canalisations de données



Dans certains cas, l'utilisation de la table de conception sera très lourde. Dans de tels cas, vous pouvez utiliser le type de passage de paramètre suivant:



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


Ici, le décalage gauche << est un opérateur groovy surchargé qui agit désormais comme un ajout à la liste.



Pour chaque itération de test, les données suivantes de la liste seront demandées pour chaque variable:



1 itération: a = 1, b = 3, c = 3;

2ème itération: a = 7, b = 4, c = 7;

3 itération: a = 0, b = 0, c = 0.



De plus, les données d'entrée peuvent non seulement être transmises explicitement, mais également être demandées, si nécessaire, à diverses sources. Par exemple, à partir de la base de données:



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


Affectation des variables de données



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


Ici, nous voyons la variable c calculée dynamiquement dans les données de test.



Combinaison de différents types de transfert de paramètres



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


Personne ne vous interdit d'utiliser plusieurs types de transfert à la fois, si nécessaire.



Un exemple d'implémentation d'un test paramétré sur le 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"
}


Ici, nous voyons le bloc where déjà familier, dans lequel les clés des paramètres (connexions et mots de passe) sont définies, qui sont stockées dans le fichier de configuration.



En raison des particularités de l'implémentation utilisée de la spécification, le cycle de configuration du pilote Web et sa fermeture (et donc la fermeture du navigateur) seront effectués pour chaque paramètre, ce qui affectera négativement le temps d'exécution du test. Nous vous suggérons de finaliser la spécification et d'améliorer le temps d'exécution du test.



Un exemple de mise en œuvre d'un test paramétré avec une spécification modifiée



Avant révision



abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver


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

    void cleanup() {
        driver.quit()
    }

}


Après révision



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


Dans la spécification mise à jour, nous voyons que la procédure de création d'un pilote Web ne sera exécutée que lors de la configuration de la classe de spécification et de la fermeture du navigateur uniquement après la fin des tests de la spécification. Dans la méthode setup (), nous voyons le même code pour obtenir l'adresse Web du service et l'ouvrir dans le navigateur, et dans la méthode cleanup (), nous allons sur www.anywebservice.ru/logout pour terminer de travailler avec le service pour l'utilisateur actuel et supprimer les cookies (pour tester le service web actuel, cette procédure est suffisante pour simuler un lancement "unique"). Le code de test lui-même n'a pas changé.



En conséquence, avec l'aide d'améliorations simples, nous avons obtenu au moins deux fois moins de temps de fonctionnement de l'autotest, par rapport à la mise en œuvre initiale.



Comparaison des tests pour testNG, pytest, pytest-bdd



Pour commencer, nous examinerons l'implémentation d'un test sur le framework de test testNG dans le langage de programmation Java, qui, comme Spock Framework, est inspiré du framework jUnit et prend en charge les tests pilotés par les données.



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


Ici, nous pouvons voir la classe de test avec toutes les méthodes setup (), cleanup () nécessaires, ainsi que le paramétrage du test sous la forme d'une méthode getUserData () supplémentaire avec l'annotation @DataProvider, qui semble un peu lourde après ce que nous avons examiné dans le test avec Spock Cadre. Aussi, pour comprendre ce qui se passe dans le test, des commentaires similaires à la description des étapes ont été laissés.



Il est à noter que testNG, contrairement à Spock Framework, prend en charge l'exécution de tests parallèles.







Ensuite, passons à un test utilisant le framework de test pytest dans le langage de programmation Python.



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


Ici, nous voyons également la prise en charge des tests pilotés par les données en tant que construction distincte, similaire à @DataProvider dans testNG. La méthode de configuration du pilote Web est "masquée" dans le montage du pilote. Grâce au typage dynamique et aux fixtures pytest, le code de ce test semble plus propre que Java.







Ensuite, passons à un aperçu du code de test en utilisant le plugin pytest-bdd, qui vous permet d'écrire des tests sous la forme de fichiers de fonctionnalités Gherkin (approche BDD pure).



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


L'un des avantages est qu'il s'agit toujours d'un framework pytest, qui possède de nombreux plugins pour diverses situations, y compris pour exécuter des tests en parallèle. L'inconvénient est la pure approche BDD elle-même, qui limitera constamment le développeur avec ses propres caractéristiques. Spock Framework permet d'écrire un code plus concis et plus facile à concevoir que le bundle PyTest + pytest-bdd.







Conclusion



Dans cet article, nous avons examiné comment simplifier l'utilisation de BDD à l'aide du framework Spock. En résumé, soulignons brièvement les principaux, à notre avis, les avantages et les inconvénients de Spock par rapport à d'autres cadres de test courants.



Avantages:



  • L'utilisation des principes BDD au lieu d'une approche BDD pure vous donne plus de flexibilité lors de l'écriture des tests.
  • La spécification de test écrite est également la documentation du système.
  • .
  • groovy ( , , closures ).


:



  • groovy. , , IDE , . Intellij IDEA, , , , .
  • groovy JVM -. , groovy, , . java, groovy .
  • L'ensemble d'extensions n'est pas aussi étendu que celui de testNG, par exemple. En conséquence, il n'y a pas d'exécution de test parallèle. Il est prévu d'ajouter cette fonctionnalité, mais le moment de leur mise en œuvre est inconnu.


En fin de compte, le Spock Framework mérite sans aucun doute l'attention car il convient à la résolution du problème commun complexe de l'automatisation des scénarios d'entreprise pour les produits logiciels avec une complexité élevée et organisationnelle.



Que pouvez-vous lire d'autre:






All Articles