Aller au contenu

Utilisation du BDD avec Cucumber dans Squash - Automatisation des cas de test

Récupération des cas de test

Annita
Administratrice Squash TM
Pravash
Product owner
Fabrice
Testeur fonctionnel
Antonine
Automaticienne
Configurer Squash TM
Rédiger les exigences
Rédiger les cas de test
Transmettre les cas de test
Récupérer les cas de test
Automatiser les cas de test
Fournir les cas de test automatisés
Indiquer que l’automatisation est effectuée
Exécuter les tests automatisés

Sélection des cas de test à automatiser dans Squash TM

Antonine choisit des tests à automatiser dans l'onglet "À traiter" de l'Espace Automatisation (voir documentation Squash TM) en les sélectionnant et en cliquant sur le bouton "Me l’assigner".

test case pick up

Ensuite, en utilisant l'onglet "M’étant assigné" de l'Espace Automatisation (voir documentation Squash TM), elle indique que l'automatisation de ces tests est lancée :

test case automation in progress

Récupération des fichiers feature

Antonine met à jour son dépôt local pour récupérer les fichiers feature poussés par Squash TM dans le dépôt Git distant.

git pull

Automatisation des cas de test

Annita
Administratrice Squash TM
Pravash
Product owner
Fabrice
Testeur fonctionnel
Antonine
Automaticienne
Configurer Squash TM
Rédiger les exigences
Rédiger les cas de test
Transmettre les cas de test
Récupérer les cas de test
Automatiser les cas de test
Fournir les cas de test automatisés
Indiquer que l’automatisation est effectuée
Exécuter les tests automatisés

Configuration du code banal (boiler plate)

Avant de mettre en œuvre les premières étapes avec Cucumber, Antonine doit configurer correctement son projet Maven.

Fichier .gitignore

Antonine crée le fichier .gitignore habituel. Il définit les fichiers que Git ne doit pas suivre et, par conséquent, ne pas prendre en compte pour les commits, voir la documentation de Git. Ici, elle veut que Git ignore les fichiers générés par Maven.
Le fichier contient :

# ignore les fichiers créés par Maven (Java compilé, rapports de test…)
target/

pom.xml file

Elle crée le fichier pom.xml suivant pour utiliser Cucumber, JUnit et Selenium :
(La documentation de Maven décrit sa syntaxe et son fonctionnement.)

<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://maven.apache.org/POM/4.0.0"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>prestashoptest</groupId>
    <artifactId>prestashoptest</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>7.2.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.platform</groupId>
            <artifactId>junit-platform-suite</artifactId>
            <version>1.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>7.2.3</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-java</artifactId>
            <version>4.1.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <encoding>UTF-8</encoding>
                    <source>11</source>
                    <target>11</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.0.0-M5</version>
            </plugin>
        </plugins>
    </build>
</project>

Fichier src/test/resources/cucumber.properties

Elle crée également le fichier src/test/resources/cucumber.properties afin d'éviter les messages de Cucumber concernant la publication des rapports. Le fichier contient :

cucumber.publish.quiet=true
cucumber.publish.enabled=false

Configuration de Cucumber dans le fichier RunCucumberTest.java

Antonine crée le fichier de test racine src/test/java/prestashoptest/RunCucumberTest.java :

package prestashoptest;

import org.junit.runner.RunWith;
import io.cucumber.junit.Cucumber;

@RunWith(Cucumber.class)
public class RunCucumberTest {
}

Structure de base du code

Antonine crée quelques répertoires dans src/test/java/prestashoptest pour organiser son code. L’arborescence des fichiers qui en résulte est la suivante :

  • .git/ → Git

  • src/

    • test /

      • java /

        • prestashoptest/

          • datatypes/ → types de données nécessaires pour modéliser les objets métiers
          • helpers/ → aides techniques (StringHelper …)
          • pageobjects/ → page objects
            • AccountCreationPageObject.java
            • CartPageObject.java
            • CatalogPageObject.java
            • HomePageObject.java
            • IdentityPageObject.java
            • ProductPageObject.java
            • SignInPageObject.java
          • seleniumtools/ → outils Selenium (aides pour l'utilisation de Selenium)
          • stepdefinitions/ → définition des pas Cucumber
            • AccountStepDefinitions.java
            • CartStepDefinitions.java
            • CommonStepDefinitions.java
          • RunCucumberTest.java
          • SetupTeardown.java
      • resources/

        • prestashoptest/ → fichiers feature générés Squash TM 🛑 Ces fichiers ne doivent pas être modifiés !

          • account

            • 431_Create_an_account__then_check_login_and_personal_data.feature
            • 432_Create_an_account__then_try_to_login_with_incorrect_password.feature
          • cart

            • 240_Add_one_product_in_the_cart.feature
            • 303_Add_two_products_in_the_cart.feature
            • 304_Add_twice_a_product_to_the_cart.feature
        • cucumber.properties → configuration de Cucumber

  • .gitignore

  • pom.xml

Quelques notes de conception

graph TD
    A[Step definitions] --> B[Page objects]
    A --> C[Data types]
    A --> D[Helpers]
    B --> E[Selenium tools]
    B --> C
    B --> D
Antonine veut séparer clairement les préoccupations :

  • src/test/java/prestashoptest/seleniumtools
    • contient la plus grande partie du code lié à l'interface avec WebDriver ;
    • est une couche technique, elle ne connaît pas les règles métier.
  • src/test/java/prestashoptest/pageobjects contient les page objects habituels.
    Un page object est une classe Java qui encapsule les détails techniques d'une page testée (ID, noms, XPaths… des éléments HTML).
    Ce modèle de conception présente deux avantages principaux :

    • Cela réduit le coût de maintenance en centralisant les détails des pages en un seul endroit : si un développeur modifie un élément HTML sur la page et que ce changement casse le localisateur utilisé par Antonine pour trouver l’élément, elle n'aura à le mettre à jour qu'à un seul endroit.
    • Cela fournit une API qui a une sémantique métier : le code utilisant le page object connaît les fonctionnalités supportées par la page (par exemple, entrer email et mot de passe, puis essayer de se connecter) mais il n’a pas besoin de connaître la structure HTML ou le comportement JavaScript de celle-ci.

    Les page objects utilisent WebDriver principalement via les classes seleniumtools (par exemple, pour toutes les opérations de base telles que la définition de la valeur d'un champ, l'obtention de la valeur d'un champ, le clic sur un élément…). Dans quelques cas, quand un page object doit effectuer des opérations qui sont trop spécifiques pour être dans une classe seleniumtools, il appelle WebDriver directement.

  • src/test/java/prestashoptest/stepdefinitions contient l'implémentation des étapes Cucumber.
    Chaque étape Cucumber interagit avec certains page objects. Elle n'a aucune connaissance du WebDriver, de la structure HTML, du code JavaScript…

Le répertoire src/test/resources/prestashoptest

🛑 Le contenu du répertoire src/test/resources/prestashoptest ne doit pas être modifié, il est contrôlé par Squash TM.
La modification de certains fichiers de ce répertoire pourrait entraver la transmission de nouveaux cas de test ou de cas de test mis à jour (c'est-à-dire les fichiers feature) à l'avenir.
Mais, même si la transmission fonctionnait, toutes les modifications seraient perdues, écrasées par Squash TM.

Écriture des outils Selenium

Antonine utilise l'héritage pour faciliter les appels Selenium : src/test/java/prestashoptest/seleniumtools/PageObjectBase.java est la classe de base pour tous les page objects.
Antonine y place des méthodes utilitaires basiques pour

  • remplir un champ et récupérer sa valeur

    protected void fillFieldValue(final SelectBy by,
                                  final String fieldKey,
                                  final String value) {
        final WebElement element = by.findElement(getDriver(), fieldKey);
        element.clear();
        element.sendKeys(value);
    }
    protected String getFieldValue(final SelectBy by,
                                   final String fieldKey) {
        return by.findElement(getDriver(), fieldKey).getAttribute("value");
    }
    

  • cliquer sur un élément

    protected void clickElement(final SelectBy by,
                                final String elementKey) {
        by.findElement(getDriver(), fieldKey).click();
    }
    

  • obtenir l'état d'une case à cocher

    protected boolean isCheckBoxSelected(final SelectBy by,
                                         final String checkBoxKey) {
        return by.findElement(getDriver(), checkBoxKey).isSelected();
    }
    

  • et ainsi de suite…

Elle contient également quelques méthodes statiques pour déclarer l'hôte où le SUT est déployé, pour sélectionner le navigateur à utiliser pour le test, pour générer une capture d'écran, pour quitter la session WebDriver… (Ces méthodes sont principalement utilisées lors du setup et du teardown des tests décrits ci-dessous).

    public static void setHost(final String host) {
        PageObjectBase.host = host;
    }
    public static void setBrowser(final Browser browser) {
        PageObjectBase.browser = browser;
    }
    public static void quit() {
        PageObjectBase.browser.quit();
    }
    public static byte[] getScreenshot() {
        return ((TakesScreenshot)getDriver()).getScreenshotAs(OutputType.BYTES);
    }

Écriture du setup et du teardown

Antonine implémente ensuite le setup et le teardown des tests qu'elle place dans src/test/java/prestashoptest/SetupTeardown.java.

  • Le setup des tests :

    @Before
    public void setup() {
        PageObjectBase.setBrowser(PageObjectBase.Browser.FIREFOX);
        PageObjectBase.setHost("http://localhost:8080/fr");
    }
    
    Ce setup initialise le type de navigateur et l'URL de l'application testée.

  • Le teardown des tests :

    @After
    public void closeBrowser(final Scenario scenario) {
        if (scenario.isFailed()) {
            final byte[] screenshot = PageObjectBase.getScreenshot();
            final String screenshotName = "screenshot " + scenario.getName() + " (line " + scenario.getLine() + ")";
            scenario.attach(screenshot,"image/png", screenshotName);
        }
        PageObjectBase.quit();
    }
    
    Ce teardown génère une capture d'écran et la transmet à Cucumber en cas d'échec du test. Cela sera très utile pour analyser la raison de l'échec.
    Le teardown permet également de quitter la session WebDriver.

Écriture des page objects

Une fois ce code de base écrit, Antonine crée un page object pour chaque page avec laquelle un pas de test Cucumber va interagir.

La plupart des page objects sont très simples et n'utilisent que les méthodes de la classe PageObjectBase.
Par exemple, pour la page de création de compte : Prestashop account creation le page object (fichier src/test/java/prestashoptest/pageobjects/AccountCreationPageObject.java) est :

package prestashoptest.pageobjects;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

import prestashoptest.datatypes.Gender;
import prestashoptest.seleniumtools.PageObjectBase;

/**
 * Account creation page
 */
public class AccountCreationPageObject extends PageObjectBase {

    public AccountCreationPageObject() {
        super("connexion?create_account=");
    }

    /**
     * Fill the form with the specified values (default values are used for the other fields)
     * and initiate the account creation.
     *
     * @param gender
     * @param firstName
     * @param lastName
     * @param password
     * @param email
     * @param birthDate
     * @param acceptPartnerOffers
     * @param acceptPrivacyPolicy
     * @param acceptNewsletter
     * @param acceptGdpr
     */
    public void fillNewAccountFields(final Gender gender,
                                     final String firstName,
                                     final String lastName,
                                     final String password,
                                     final String email,
                                     final LocalDate birthDate,
                                     final boolean acceptPartnerOffers,
                                     final boolean acceptPrivacyPolicy,
                                     final boolean acceptNewsletter,
                                     final boolean acceptGdpr) {
        fillGender(gender);
        fillFirstName(firstName);
        fillLastName(lastName);
        fillEmail(email);
        fillPassword(password);
        fillBirthDate(birthDate);
        if (acceptPartnerOffers) acceptPartnerOffers();
        if (acceptPrivacyPolicy) acceptPrivacyPolicy();
        if (acceptNewsletter) acceptNewsletter();
        if (acceptGdpr) acceptGdpr();
        submitNewAccountForm();
    }

    /**
     * Fill the gender field
     * If the gender is undefined, the field is untouched.
     * 
     * @param gender
     */
    public void fillGender(final Gender gender) {
        if (gender.equals(Gender.UNDEFINED)) {
            return;
        }
        final String id = (gender.equals(Gender.MALE)) ? "field-id_gender-1"
                                                       : "field-id_gender-2";
        clickElement(SelectBy.ID, id);
    }

    /**
     * Fill the first name field
     * 
     * @param firstName
     */
    public void fillFirstName(final String firstName) {
        fillFieldValue(SelectBy.ID, "field-firstname", firstName);
    }

    /**
     * Fill the last name field
     * 
     * @param lastName
     */
    public void fillLastName(final String lastName) {
        fillFieldValue(SelectBy.ID, "field-lastname", lastName);
    }

    /**
     * Fill the email field
     * 
     * @param email
     */
    public void fillEmail(final String email) {
        fillFieldValue(SelectBy.ID, "field-email", email);
    }

    /**
     * Fill the password field
     * 
     * @param password
     */
    public void fillPassword(final String password) {
        fillFieldValue(SelectBy.ID, "field-password", password);
    }

    /**
     * Fill the password field
     * 
     * @param birthDate
     */
    public void fillBirthDate(final LocalDate birthDate) {
        fillFieldValue(SelectBy.ID, "field-birthday", birthDate.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")));
    }

    /**
     * Approve for partner offers
     */
    public void acceptPartnerOffers() {
        clickElement(SelectBy.NAME, "optin");
    }

    /**
     * Approve the customer privacy policy
     */
    public void acceptPrivacyPolicy() {
        clickElement(SelectBy.NAME, "customer_privacy");
    }

    /**
     * Sign up for the newsletter
     */
    public void acceptNewsletter() {
        clickElement(SelectBy.NAME, "newsletter");
    }

    /**
     * Approve the GDPR policy
     */
    public void acceptGdpr() {
        clickElement(SelectBy.NAME, "psgdpr");
    }

    /**
     * Initiate the account creation
     */
    private void submitNewAccountForm() {
        clickElement(SelectBy.XPATH, "//*[@id=\"customer-form\"]/footer/button");
    }
}

Dans certains cas, il est plus difficile de naviguer dans l'HTML, donc le page object est plus complexe.
Par exemple, pour récupérer le contenu du panier : Prestashop account creation le page object (fichier src/test/java/prestashoptest/pageobjects/CartPageObject.java) est :

package prestashoptest.pageobjects;

import java.util.ArrayList;
import java.util.List;

import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;

import prestashoptest.datatypes.CartLine;
import prestashoptest.seleniumtools.PageObjectBase;

public class CartPageObject extends PageObjectBase {

    public CartPageObject() {
        super("panier?action=show");
    }

    public List<CartLine> getContent() {
        final List<WebElement> items = getElements(SelectBy.CLASS, "cart-item");
        final List<CartLine> list = new ArrayList<CartLine>(items.size());
        for (final WebElement item: items) {
            final WebElement productElem = item.findElement(By.className("label"));
            final String product = productElem.getText();
            final WebElement quantityElem = item.findElement(By.name("product-quantity-spin"));
            final int quantity = Integer.parseInt(quantityElem.getAttribute("value"));
            final List<WebElement> dimensionElem = item.findElements(By.className("dimension"));
            final String dimension = dimensionElem.isEmpty() ? null : dimensionElem.get(0).findElement(By.className("value")).getText();  
            final List<WebElement> sizeElem = item.findElements(By.className("taille"));
            final String size = sizeElem.isEmpty() ? null : sizeElem.get(0).findElement(By.className("value")).getText();  
            final List<WebElement> colorElem = item.findElements(By.className("couleur"));
            final String color = colorElem.isEmpty() ? null : colorElem.get(0).findElement(By.className("value")).getText();  
            list.add(new CartLine(product, quantity, dimension, size, color));
        }
        return list;
    }
}

Implémentation des pas de test Cucumber

Les fichiers src/test/java/prestashoptest/stepdefinitions/*.java contiennent les définitions des pas de test Cucumber. Antonine regroupe les pas de test liés à un domaine de fonctionnalités donné dans une même classe. Elle crée donc AccountStepDefinitions.java, CartStepDefinitions.java… Enfin, CommonStepDefinitions.java contient des pas de test transverses.

Il y a trois types de pas :

Given

Un pas Given est utilisé pour établir une précondition de test.
Il peut naviguer et interagir avec plusieurs pages afin de préparer les données nécessaires. Il peut même configurer les données en contournant l'interface utilisateur de l'application : en appelant l'API REST fournie par l'application, en injectant des données dans la base de données de l'application…
Un pas Given n'a pas besoin d'être sur une page particulière pour être appelé.

Par exemple, certains cas de test ont besoin que l’utilisateur (non spécifique) soit connecté. Donc ils utilisent le pas Given I am logged in.
Ce pas est implémenté en utilisant la page de création de compte et en créant un utilisateur temporaire (fichier src/test/java/prestashoptest/stepdefinitions/AccountStepDefinitions.java) :

package prestashoptest.stepdefinitions;


import io.cucumber.java.en.Given;


public class AccountStepDefinitions {

    /**
     * Create a temporary user and keep him/her logged in
     */
    @Given("I am logged in")
    public void createTemporaryUser() {
        final String id = StringsHelper.generateRandomId();
        final AccountCreationPageObject newAccountPage = new AccountCreationPageObject();
        newAccountPage.goTo();
        newAccountPage.fillNewAccountFields(Gender.UNDEFINED,
                                            "first. " + id,
                                            "last. " + id,
                                            id,
                                            id + "@example.com",
                                            LocalDate.of(2000, 1, 1),
                                            true,
                                            true,
                                            true,
                                            true);
        final HomePageObject homePage = new HomePageObject();
        homePage.assertIsCurrent();
    }

   
}
(Pour avoir des pas de test robustes, Antonine vérifie ici que la création du compte a été un succès en s’assurant que l’utilisateur a bien été redirigé sur la page d’accueil).

When

Un pas When exécute une action.

Par example, When I am on the AccountCreation page est implémenté (dans le même fichier) par :

    /**
     * Go to the Account Creation page
     */
    @When("I am on the AccountCreation page")
    public void displayNewAccountPage() {
        final AccountCreationPageObject newAccountPage = new AccountCreationPageObject();
        newAccountPage.goTo();
    }

De nombreux pas de test When nécessitent d'être exécutés alors qu'une page donnée est affichée, ils vérifient donc que c'est bien le cas. Par exemple, l'implémentation du pas When("I fill AccountCreation fields with gender {string} firstName {string} lastName {string} password {string} email {string} birthDate {string} acceptPartnerOffers {string} acceptPrivacyPolicy {string} acceptNewsletter {string} acceptGdpr {string} and submit est :

    /**
     * Fill the fields of the Account Creation page and submit the form
     *
     * The current page must be the Account Creation page
     */
    @When("I fill AccountCreation fields with gender {string} firstName {string} lastName {string} password {string} " +
          "email {string} birthDate {string} acceptPartnerOffers {string} acceptPrivacyPolicy {string} acceptNewsletter{string} acceptGdpr {string} and submit")
    public void fillAccountCreationFieldsAndSubmit(final String genderCode,
                                                   final String firstName,
                                                   final String lastName,
                                                   final String password,
                                                   final String email,
                                                   final String birthDate,
                                                   final String acceptPartnerOffers,
                                                   final String acceptPrivacyPolicy,
                                                   final String acceptNewsletter,
                                                   final String acceptGdpr) {
        final AccountCreationPageObject newAccountPage = new AccountCreationPageObject();
        newAccountPage.assertIsCurrent();
        newAccountPage.fillNewAccountFields(Gender.ofCode(genderCode),
                                            firstName,
                                            lastName,
                                            password,
                                            email,
                                            LocalDate.parse(birthDate),
                                            StringsHelper.convertYesNoIntoBoolean(acceptPartnerOffers),
                                            StringsHelper.convertYesNoIntoBoolean(acceptPrivacyPolicy),
                                            StringsHelper.convertYesNoIntoBoolean(acceptNewsletter),
                                            StringsHelper.convertYesNoIntoBoolean(acceptGdpr));
    }

Then

Un pas Then est utilisé pour vérifier qu'une postcondition de test est remplie.
Il peut naviguer et interagir avec plusieurs pages afin de vérifier que les données sont conformes aux attentes. Il peut même récupérer les données en contournant l'interface utilisateur de l'application : en appelant l'API REST fournie par l'application, en interrogeant la base de données de l'application…
Un pas Then n'a pas besoin d'être sur une page particulière pour être appelé.

Par exemple, l'implémentation du pas Then My personal information is gender {string} firstName {string} lastName {string} email {string} birthDate {string} acceptPartnerOffers {string} acceptPrivacyPolicy {string} acceptNewsletter {string} acceptGdpr {string} est réalisée en affichant la page des données personnelles, en récupérant les valeurs des champs et en vérifiant qu'elles ont les valeurs attendues :

    /**
     * Verify that the personal data is equal to the given parameters
     */
    @Then("My personal information is gender {string} firstName {string} lastName {string} " +
          "email {string} birthDate {string} acceptPartnerOffers {string} acceptPrivacyPolicy {string} acceptNewsletter {string} acceptGdpr {string}")
    public void assertPersonalInformation(final String genderCode,
                                          final String firstName,
                                          final String lastName,
                                          final String email,
                                          final String birthDate,
                                          final String acceptPartnerOffers,
                                          final String acceptPrivacyPolicy,
                                          final String acceptNewsletter,
                                          final String acceptGdpr) {
        final IdentityPageObject identityPage = new IdentityPageObject();
        identityPage.goTo();
        Assertions.assertEquals(Gender.ofCode(genderCode),
                                identityPage.getGender(),
                                "The effective gender is not the expected one");
        Assertions.assertEquals(firstName,
                                identityPage.getFirstName(),
                                "The effective first name is not the expected one");
        Assertions.assertEquals(lastName,
                                identityPage.getLastName(),
                                "The effective last name is not the expected one");
        Assertions.assertEquals(email,
                                identityPage.getEmail(),
                                "The effective email is not the expected one");
        Assertions.assertEquals(LocalDate.parse(birthDate),
                                identityPage.getBirthDate(),
                                "The effective birth date is not the expected one");
        Assertions.assertEquals(StringsHelper.convertYesNoIntoBoolean(acceptPartnerOffers),
                                identityPage.doesAcceptPartnerOffers(),
                                "The effective acceptPartnerOffers is not the expected one");
        Assertions.assertEquals(StringsHelper.convertYesNoIntoBoolean(acceptPrivacyPolicy),
                                identityPage.doesAcceptPrivacyPolicy(),
                                "The effective acceptPrivacyPolicy is not the expected one");
        Assertions.assertEquals(StringsHelper.convertYesNoIntoBoolean(acceptNewsletter),
                                identityPage.doesAcceptNewsletter(),
                                "The effective acceptNewsletter is not the expected one");
        Assertions.assertEquals(StringsHelper.convertYesNoIntoBoolean(acceptGdpr),
                                identityPage.doesAcceptGdpr(),
                                "The effective acceptGdpr is not the expected one");
    }

Quelques détails d'implémentation

Expressions régulières et expressions Cucumber

Il existe deux façons d'écrire les patterns correspondant aux paramètres des pas Gherkin :

  • les expressions régulières Java usuelles :

    public class CartStepDefinitions {
        @Then("I have (\\d+) products in my cart")
        public void assertNumberOfProductsInCart(final String argNbProducts) {
            int nbProducts = Integer.parseInt(argNbProducts);
            System.out.format("Products: %n\n", nbProducts);
        }
    }
    

  • les expressions Cucumber :

    public class CartStepDefinitions {
        @Then("I have {int} products in my cart")
        public void assertNumberOfProductsInCart(final int nbProducts) {
            System.out.format("Products: %n\n", nbProducts);
        }
    }
    
    Elles permettent de faire correspondre rapidement les données usuelles (string, word, int, float…) sans avoir à écrire le code boiler pour convertir le texte en nombres entiers, nombres décimaux… donc Antonine utilise celles-ci.

Lambdas vs. méthodes classiques

Il y a deux façons d'implémenter les pas Gherkin :

  • en utilisant des méthodes classiques :

    package prestashoptest.stepdefinitions;
    import io.cucumber.java.en.Then;
    
    public class CartStepDefinitions {
        @Then("I have {int} products in my cart")
        public void assertNumberOfProductsInCart(final int nbProducts) {
            System.out.format("Products: %n\n", nbProducts);
        }
    }
    
    Pour cela, la dépendance suivante doit être déclarée dans le fichier pom.xml :
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java</artifactId>
        <version>7.2.3</version>
        <scope>test</scope>
    </dependency>
    

  • en utilisant des lambdas :

    package prestashoptest.stepdefinitions;
    import io.cucumber.java8.En;
    
    public class CartStepDefinitions implements En {
        public CartStepDefinitions() {
            Then("I have {int} products in my cart", (final Integer nbProducts) -> {
                System.out.format("Products: %n\n", nbProducts);
            });
        }
    }
    
    Pour cela, la dépendance suivante doit être ajoutée dans le fichier pom.xml :
    <dependency>
        <groupId>io.cucumber</groupId>
        <artifactId>cucumber-java8</artifactId>
        <version>7.2.3</version>
        <scope>test</scope>
    </dependency>
    

La deuxième méthode nécessite moins de code, il n'est pas nécessaire de définir le nom de méthode inutile assertNumberOfProductsInCart. Mais Cucumber ne supporte les méthodes lambdas que jusqu'à 9 paramètres dans un pas Gherkin donnée.
Fabrice et Antonine se sont mis d'accord pour avoir un seul pas pour remplir un formulaire de demande donné afin de garder les pas simples pour Fabrice. Mais le formulaire de création de compte a 10 paramètres, donc Antonine s'en tient aux méthodes classiques.

Utiliser Maven pour générer le squelette de code

Afin de gagner du temps pour l'implémentation des pas Gherkin, Antonine utilise Maven pour générer le squelette de code. Elle lance le test :

mvn clean test -Dcucumber.features=src/test/resources/prestashoptest/Account_management/431_Create_an_account__then_check_login_and_personal_data.feature
Maven va échouer et signaler les pas non implémentés, chaque message d'erreur contient un squelette de code qu'Antonine peut copier/coller pour commencer à implémenter le pas :
[ERROR]   The step 'I fill AccountCreation fields with gender "M" firstName "John" lastName "Doe" password "mypassword" email "jdoe@example.com" birthDate "1990-01-02" acceptPartnerOffers "no" acceptPrivacyPolicy "yes" acceptNewsletter "yes" acceptGdpr "yes" and submit' is undefined.
You can implement this step using the snippet(s) below:

@When("I fill AccountCreation fields with gender {string} firstName {string} lastName {string} password {string} email {string} birthDate {string} acceptPartnerOffers {string} acceptPrivacyPolicy {string} acceptNewsletter {string} acceptGdpr {string} and submit")
public void i_fill_account_creation_fields_with_gender_first_name_last_name_password_email_birth_date_accept_partner_offers_accept_privacy_policy_accept_newsletter_accept_gdpr_and_submit(String string, String string2, String string3, String string4, String string5, String string6, String string7, String string8, String string9, String string10) {
    // Write code here that turns the phrase above into concrete actions
    throw new io.cucumber.java.PendingException();
}
(Si cucumber-java8 est utilisé, les squelettes proposés utilisent des lambdas).

Livraison des cas de test automatisés

Annita
Administratrice Squash TM
Pravash
Product owner
Fabrice
Testeur fonctionnel
Antonine
Automaticienne
Configurer Squash TM
Rédiger les exigences
Rédiger les cas de test
Transmettre les cas de test
Récupérer les cas de test
Automatiser les cas de test
Fournir les cas de test automatisés
Indiquer que l’automatisation est effectuée
Exécuter les tests automatisés

Une fois qu'Antonine a implémenté tous les pas Cucumber utilisés par les fichiers features, elle vérifie que les tests fonctionnent correctement en les exécutant :

mvn test -Dcucumber.plugin="html:target/cucumber-reports/Cucumber_html.html"
et en vérifiant le rapport Cucumber (le fichier target/cucumber-reports/Cucumber_html.html) : Cucumber report

Lorsqu'elle trouve quelque chose de suspect lors de l'automatisation d'un test, elle effectue le test manuellement et, s'il y a vraiment un bug, elle le signale comme décrit dans la documentation Squash TM.

Lorsque les tests se déroulent correctement, elle commite et pousse :

git add .
git commit -m "Implemented first account and cart steps"
git push

Indication que l’automatisation a été effectuée

Annita
Administratrice Squash TM
Pravash
Product owner
Fabrice
Testeur fonctionnel
Antonine
Automaticienne
Configurer Squash TM
Rédiger les exigences
Rédiger les cas de test
Transmettre les cas de test
Récupérer les cas de test
Automatiser les cas de test
Fournir les cas de test automatisés
Indiquer que l’automatisation est effectuée
Exécuter les tests automatisés

Ensuite, à l'aide de l'onglet "M’étant assigné" de l’Espace Automatisation (voir la documentation Squash TM), Antonine indique que les tests ont été automatisés :

test case automation done

Elle définit également la technologie des tests : test case automation done (Ce paramètre devrait être automatiquement rempli par Squash TM, cela sera corrigé dans une prochaine version).