• 8 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 18/02/2022

Testez le parcours utilisateur avec les tests fonctionnels

Dans les chapitres précédents vous avez construit une application qui rivalisera un jour avec Google (n'ayons pas peur des mots). J'espère que vous êtes fiers de vous !

Néanmoins il devient assez fastidieux de tester le parcours utilisateur. C'est pourquoi dans ce chapitre je vous propose d'automatiser ce processus en faisant appel aux tests fonctionnels !

Souvenez-vous des tests

Rappel des tests fonctionnels

Tester un projet informatique est un sujet en soi (nous avons d'ailleurs un cours sur le sujet). Il existe plusieurs types de tests dont les plus connus sont les tests unitaires.

Un test unitaire vérifie qu'une petite portion du code a bien le comportement attendu. Dans votre projet, par exemple, vous pourriez créer un test unitaire pour vérifier que l'image générée a bien les dimensions adéquates.

Les tests fonctionnels, eux, se concentrent sur une séquence d'opérations complexes, comme le parcours d'un utilisateur. Ils vérifient que le logiciel renvoie les informations attendues par l'utilisateur pendant qu'il réalise une action.

Que veut dire "parcours utilisateur" ?

Un parcours utilisateur représente les différentes étapes nécessaires pour qu'un utilisateur concrétise un objectif.

Prenons l'exemple d'un blog. Les actions sont multiples, à la fois pour le visiteur et pour l'administrateur. Les parcours sont donc très différents.

Un des parcours peut concerner l'accès à l'espace d'administration. Le parcours utilisateur est alors le suivant : "L'utilisateur arrive sur la page de connexion. Il entre son identifiant et son mot de passe. Il clique sur le bouton de connexion. Il arrive sur la page d'accueil de l'espace d'administration."

Lors d'un test fonctionnel, nous vérifions que les différentes étapes permettent effectivement d'afficher la page d'accueil de l'espace d'administration sans écueil.

Les tests fonctionnels peuvent être réalisés par un humain ou par un ordinateur. Dans les chapitres précédents, nous avons réalisé ces tests à la main. Voyons tout de suite comment les automatiser.

Découvrez le parcours utilisateur que vous allez tester

Par quel parcours utilisateur allez-vous commencer ? Je vous propose de tester que l'URL de la page de résultat est bien générée après une authentification.

Voici les étapes :

  • L'utilisateur arrive sur la page d'accueil : /index.

  • Il clique sur le bouton "Continuer avec Facebook",

  • Une fenêtre s'ouvre. Il entre son identifiant et son mot de passe. Il clique sur "Connexion". La fenêtre se referme.

  • L'URL de la page devient /result?first_name=Tom&id=111823112767411&gender=male.

Prêts ? C'est parti !

Installez les composants nécessaires

Pytest

Pytest est une des librairies de test les plus utilisées. Elle vous permet d'écrire des tests unitaires de manière très simple.

Installez-la via pip :

$ pip install pytest

Quand c'est terminé, exécutez la commande suivante pour vérifier que Pytest est bien installé :

$ pytest --version
This is pytest version 3.x.y, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py

Flask-Testing

La seconde librairie à installer est Flask-Testing.

Elle permet d'utiliser la puissance de Flask dans les tests. Vous en aurez besoin pour lancer un serveur de test.

Installez-la également avec pip :

$ pip install Flask-Testing

Selenium et Geckodriver

Enfin, vous utilisez Selenium pour mimer le comportement de l'utilisateur. Cette librairie permet d'automatiser l'interaction entre un utilisateur et un navigateur web. Plus spécifiquement, utilisez Selenium avec Python.

$ pip install selenium

Vous aurez également besoin d'un navigateur web (Firefox, Chrome...). Bien sûr, nous en avons déjà un ou plusieurs sur nos ordinateurs. Néanmoins il faut que nous installions un driver pour pouvoir les utiliser.

Le driver de Firefox est disponible à cette adresse. Téléchargez la version correspondant à votre système d'exploitation et lancez l'installation.

C'est parfait, vous avez maintenant tout ce qu'il vous faut !

Effectuez vos premiers tests

Configurez vos tests

Le fichier de configuration

Les tests se baseront sur un nouveau fichier de configuration afin de distinguer les réglages utilisés en production de ceux utilisés en test. Créez un nouveau fichier config.py dans le dossier fbapp/tests.

À l'intérieur, collez les informations dont vous aurez besoin :

config.py

import os
SECRET_KEY = 'votre_nouvelle_cle_secrete'

# Remplacez par l'id de l'app TEST que vous avez créée précédemment.
FB_APP_ID = 12345678901234567890

basedir = os.path.abspath(os.path.dirname(__file__))

# Nouvelle base de données pour les tests.
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app_test.db')

# Active le debogueur
DEBUG = True
TESTING = True
LIVESERVER_PORT = 8943
LIVESERVER_TIMEOUT = 10

# Facebook test user
FB_USER_NAME = "Ellen"
FB_USER_PW = "YOLOYOLO"
FB_USER_EMAIL = "ellen_rmilrcp_page@tfbnw.net"
FB_USER_ID = 100018814390853
FB_USER_GENDER = 'female'

Fichier de tests

Créez un nouveau fichier test_user_takes_the_test.py dans le dossier fbapp/tests.

Puis importez les librairies nécessaires :

test_user_takes_the_test.py

from flask_testing import LiveServerTestCase
from selenium import webdriver

from .. import app
from .. import models

Créez une nouvelle classe TestUserTakesTheTest qui hérite de la classe   LiveServerTestCase  et ajoutez les lignes suivantes :

test_user_takes_the_test.py

class TestUserTakesTheTest(LiveServerTestCase):
    def create_app(self):
        # Fichier de config uniquement pour les tests.
        app.config.from_object('fbapp.tests.config')
        return app

    # Méthode exécutée avant chaque test
    def setUp(self):
        """Setup the test driver and create test users"""
        # Le navigateur est Firefox
        self.driver = webdriver.Firefox()
        # Ajout de données dans la base.
        models.init_db()

    # Méthode exécutée après chaque test
    def tearDown(self):
        self.driver.quit()

Afin de vérifier que tout fonctionne, ajoutez ce premier test :

test_user_takes_the_test.py

class TestUserTakesTheTest(LiveServerTestCase):
    def test_user_login(self):
        # On ouvre le navigateur avec l'adresse du serveur.
        self.driver.get(self.get_server_url())
        # L'adresse dans l'url doit être celle que l'on attend.
        assert self.driver.current_url == 'http://localhost:8943/'

Lancez le test en exécutant la commande suivante :

$ pytest fbapp/tests/test_user_takes_the_test.py

collected 1 items
fbapp/tests/test_user_takes_the_test.py .

=========================== 1 passed in 4.83 seconds ===========================

Le navigateur s'ouvre et affiche la page d'arrivée. Tout fonctionne !

L'utilisateur clique sur le bouton "Continuer avec Facebook"

Créez une nouvelle méthode clicks_on_login() que vous appellerez dans le test :

test_user_takes_the_test.py

class TestUserTakesTheTest(LiveServerTestCase):
    def clicks_on_login(self):
        button = self.get_el(".fb-login-button")

    def test_user_login(self):
        self.driver.get(self.get_server_url())
        self.clicks_on_login()
        assert self.driver.current_url == 'http://localhost:8943/'

Comment cliquer sur le bouton ?

Ce serait assez simple si le bouton était une balise button déjà présente dans le DOM. Mais ce serait trop facile !

Le bouton est créé par le script de Facebook après le chargement de la page. Il s'agit en fait d'une iframe contenant le bouton.

Vous devez donc attendre que l'iframe ait fini de charger avant de cliquer sur l'élément, sinon Pytest renverra une erreur.

Selenium contient une méthode parfaite pour cela : wait. Voici comment l'utiliser :

import selenium.webdriver.support.ui as ui
from selenium.webdriver.common.action_chains import ActionChains

...

class TestUserTakesTheTest(LiveServerTestCase):
    def setUp(self):
        ...
        self.wait = ui.WebDriverWait(self.driver, 1000)

    def get_el(self, selector):
        return self.driver.find_element_by_css_selector(selector)

    def clicks_on_login(self):
        # Element contenant l'iframe
        button = self.get_el(".fb-login-button")
        # On attend que l'iframe soit chargée
        self.wait.until(lambda driver: self.driver.find_element_by_tag_name("iframe").is_displayed())
        # Clic sur l'élément
        ActionChains(self.driver).click(button).perform()

Lancez de nouveau le test : ça fonctionne !

L'utilisateur s'authentifie sur Facebook

L'utilisateur voit le formulaire

La fenêtre d'authentification s'ouvre. À présent, vous devez indiquer à Selenium que vous souhaitez interagir avec cette nouvelle fenêtre et non plus celle qui contient notre site.

L'objet driver contient un attribut très intéressant : window_handles. Il renvoie une liste composée de toutes les fenêtres ouvertes !

Utilisez-le comme suit :

class TestUserTakesTheTest(LiveServerTestCase):
    def sees_login_page(self):
        # On attend d'avoir plus d'une fenêtre ouverte.
        self.wait.until(lambda driver: len(self.driver.window_handles) > 1)
        # switch_to permet de changer de fenêtre.
        # window_handles renvoie une liste contenant toutes les fenêtres ouvertes
        # par ordre d'ouverture.
        # La fenêtre d'authentification étant la dernière ouverte,
        # c'est la dernière de la liste.
        self.driver.switch_to.window(self.driver.window_handles[-1])
        # On attend que la page ait fini de charger.
        self.wait.until(lambda driver: self.get_el('#email'))
        assert self.driver.current_url.startswith('https://www.facebook.com/login.php') # <3 Python

    def test_user_login(self):
        self.driver.get(self.get_server_url())
        self.clicks_on_login()
        self.sees_login_page()
        assert self.driver.current_url == 'http://localhost:8943/'

Lancez les tests de nouveau : ils cassent. C'est normal : l'URL n'est plus celle de notre site mais celle de Facebook ! La prochaine étape est de demander à l'ordinateur de remplir le formulaire.

L'utilisateur soumet le formulaire

Utilisez la méthode send_keys() pour remplir les champs du formulaire. Elle prend en paramètre une chaîne de caractères. Voici comment faire !

class TestUserTakesTheTest(LiveServerTestCase):
    def enter_text_field(self, selector, text):
        # On trouve le champ à remplir.
        text_field = self.get_el(selector)
        # On enlève les valeurs qui y sont peut-être déjà.
        text_field.clear()
        # On ajoute le texte voulu dans le champ de formulaire.
        text_field.send_keys(text)

    def submits_form(self):
        # Le champ email a l'id email
        self.enter_text_field('#email', app.config['FB_USER_EMAIL'])
        # Le champ password a l'id pass
        self.enter_text_field('#pass', app.config['FB_USER_PW'])
        # On clique sur le bouton de soumission
        self.get_el('#loginbutton input[name=login]').click()

    def test_user_login(self):
        self.driver.get(self.get_server_url())
        self.clicks_on_login()
        self.sees_login_page()
        self.submits_form()
        # On revient à notre site.
        self.driver.switch_to.window(self.driver.window_handles[0])
        # On attend que la fenêtre de Facebook se ferme,
        # donc de n'avoir plus qu'une fenêtre d'ouverte.
        self.wait.until(lambda driver: len(self.driver.window_handles) == 1)
        assert self.driver.current_url == 'http://localhost:8943/'

L'utilisateur arrive sur la page de résultats

C'est la dernière étape : vous y êtes presque !

L'URL est censée changer, vous devez donc demander au driver d'attendre un peu. Plus précisément, il doit attendre que l'URL contienne un paramètre. Cela nous évite de lui spécifier une URL et rendent donc nos tests plus flexibles.

Puis, vérifiez que l'URL de la page est bien similaire à celle attendue.

test_user_takes_the_test.py

from flask import url_for

class TestUserTakesTheTest(LiveServerTestCase):

    def setUp(self):
        ...
        self.result_page = url_for('result',
                                    first_name=app.config['FB_USER_NAME'],
                                    id=app.config['FB_USER_ID'],
                                    gender=app.config['FB_USER_GENDER'],
                                    _external=True)

    def test_user_login(self):
        # On attend que la redirection soit finie.
        self.wait.until(lambda driver: '?' in self.driver.current_url)
        # L'URL correspond au schéma attendu
        assert self.driver.current_url == self.result_page

tests/config.py

SERVER_NAME = 'localhost:8943'

Lancez le test... Tout fonctionne !

Récupérez le code du chapitre

Retrouvez l'intégralité du code de ce chapitre à cette adresse.

 

En résumé

  • Il existe plusieurs manières de tester une application, notamment avec les tests unitaires et les tests fonctionnels

  • Les tests fonctionnels permettent de vérifier que le parcours d’un utilisateur sur une application web se déroule comme attendu

  • Pour simuler l’interaction entre un utilisateur et un navigateur web, vous pouvez utiliser la librairie Selenium

  • Pour savoir quoi rédiger comme test fonctionnel, mettez-vous à la place de l’utilisateur de votre application : 

    • sur quel bouton allez-vous cliquer sur la page d’accueil ? 

    • Sur quelle page de résultat êtes-vous censés atterrir ?
      Il ne vous reste qu’à transformer ce parcours utilisateur en test fonctionnel.

Dans le prochain chapitre, vous verrez comment déployer votre application en production.

Exemple de certificat de réussite
Exemple de certificat de réussite