• 10 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 25/04/2022

Organisez vos tests en classes

Comme vous avez pu le remarquer lors de la mise en place des différents scénarios de tests, vous êtes parfois obligé de dupliquer du code pour implémenter le contexte du test ou une partie du scénario. Or, dupliquer ce code à chaque écriture d'un test serait vraiment fastidieux. D’autant que cela nous obligerait à nous répéter, et ce ne serait pas très maintenable par la suite.

J'aimerais bien déclarer ces variables et les réutiliser dans mes fonctions qui testent des méthodes de classe, ou même utiliser à nouveau des lignes de code identiques dans plusieurs tests sans avoir à recopier l’ensemble des lignes.

Avez-vous une idée de la manière dont nous pourrions procéder ?

Je vous laisse réfléchir...

Alors, vous avez une superbe idée ?

Et si nous créions une classe dans nos tests ? Nous pourrions ainsi déclarer des attributs de classe et des méthodes, afin de les réutiliser chaque fois que nécessaire. Cela nous permettrait également de bien différencier nos tests.

Pourquoi utiliser une classe de test ? 

Pour :

  • regrouper l’ensemble des tests d’une classe ;

  • définir un environnement fixe pour l'exécution des méthodes de test ;

  • éliminer la duplication du code commun pour tous les cas de test ;

  • faciliter le refactoring des tests. 

Créez une classe de test

Le concept a l’air compliqué, mais c’est tout le contraire. D’abord, vous devez créer une nouvelle classe dans un fichier de test. Cette classe de test portera le nom de la classe à tester, précédé parTest. Ce préfixe permettra de spécifier que c’est une classe de test. Vous pouvez ensuite implémenter chaque test en tant que méthode de cette classe.

Voici un exemple de classe de test avec deux tests :

class TestClass:
    def test_one(self):
        assert 1 == 1

    def test_two(self):
        assert 2 == 2

Lançons maintenant la commandepytestpour exécuter nos tests. Nous avons le résultat suivant dans le terminal :

Les deux tests de la classe de tests sont lancés une fois que la commande `pytest`est exécutée sur le terminal.
Lancer les tests d’une classe de test

Grâce à l’option-vaprès la commandepytest, vous pouvez savoir à quel fichier et à quelle classe chaque test appartient :

L’ajout de l’option `-v` permet d’avoir plus de détails sur les tests lancés.
Encore plus de détails !

Vous pouvez voir dans le screenshot ci-dessus, qu’il y a bien deux tests. Et dans les traces, nous pouvons également voir que les tests appartiennent au fichiertest_class.pyet à la classe de testTestClass. Cette commande est donc très pratique !

Je vais maintenant délibérément soulever une assertion sur le deuxième test pour vous montrer comment débugger un test en utilisant une classe de test. C’est relativement la même chose que ce que nous avons vu dans le chapitre Allez plus loin avec Pytest. Voici la même classe avec une erreur dans le deuxième test :

class TestClass:
    def test_one(self):
        assert 1 == 1

    def test_two(self):
        assert 2 == 1

Voyons voir ce que le terminal va nous afficher après le lancement des tests :

Comment débugger une erreur ?
Comment débugger une erreur ?

Pas de surprise, il y a du rouge partout ! Effectivement, nous avions ajouté une erreur dans le deuxième test, et nous pouvons voir dans les traces du terminal que le test qui se nommetest_twode la classeTestClassne passe pas. Néanmoins, il indique l’assertion qui a été soulevée. Vous pouvez donc maintenant facilement corriger l’erreur et relancer les tests pour valider la correction.

Exécutez des actions avant ou après des tests

J’ai gardé le meilleur pour la fin ! Vous savez maintenant que l’une des parties qui prennent le plus de temps lors de l’implémentation, c’est la mise en place du contexte et de l’environnement de test. C’est pourquoi je vais vous montrer dans cette partie du chapitre comment vous faciliter la vie lors de l’implémentation des tests.

Laissez-moi vous présenter les méthodessetup()etteardown()!

Pytest vous permet d'exécuter des instructions avant ou après chaque test unitaire. Cette fonctionnalité de notre librairie de tests porte le doux nom de setup et teardown.

Il existe d’une part une méthode de test appeléesetupqui est invoquée avant le lancement du test. Vous pouvez ainsi configurer le contexte et l’environnement de votre test dans cette méthode, pour remplir la base de données ou même inscrire et connecter un utilisateur, par exemple.

D’autre part, il existe une deuxième méthode appeléeteardown, qui sera invoquée à la fin de votre test. Vous pouvez utiliser cette méthode pour nettoyer les objets que vous avez utilisés pour vos tests.

Il existe plusieurs niveaux de déclenchement : avant chaque test unitaire, à la création d'une classe ou à l'import du module Pytest. Nous allons voir les fonctionssetupetteardownappliquées à plusieurs niveaux :

  • d’un module ;

  • d’une classe ; 

  • d’une méthode ;

  • d’une fonction.

Setup / teardown au niveau du module

Au niveau du module, il existe deux fonctions :

  • setup_module, qui est appelée au début du module ;

  • teardown_module, qui est appelée à la fin du module. 

Ces deux fonctions permettent donc de configurer l’environnement de manière globale, à tous les tests et classes contenus dans le module. Vous pouvez ainsi ajouter votre configuration dans le bloc des deux fonctions. Notez que ces deux fonctions seront appelées une fois pour tous les tests.

def setup_module(module):
    """ fonction appelée lors de l’import d’un module """

def teardown_module(module):
    """ fonction appelée à la fin de l’exécution de tous les tests d’un module """

Setup / teardown au niveau d’une classe

Cette fois-ci, les méthodessetup_classetteardown_classsont respectivement appelées à l’instanciation et à la destruction d’une classe. La configuration du contexte ne concerne que les scénarios de cette classe de test. Il est important de noter que ces méthodes ne seront lancées qu’une seule fois pour l’ensemble des tests de la classe.

@classmethod
def setup_class(cls):
    """ fonction appelée lors de la création de la classe"""

@classmethod
def teardown_class(cls):
    """ fonction appelée lors de la destruction de la classe"""

Setup / teardown au niveau d’une méthode

Voici maintenant les deux méthodes les plus intéressantes :setup_methodetteardown_method. Effectivement, dans les cas précédents les méthodes permettaient de configurer un environnement commun à plusieurs tests. Cependant cette pratique peut être dangereuse

Vous allez me demander pourquoi. C’est simple : car en utilisant les méthodes précédentes, on rend les tests interdépendants. Par conséquent, il est possible qu’un test modifie l’environnement de test, car chaque test partage une même configuration et potentiellement une même base de données. Or, tout changement dans l’ordre de lancement des tests peut entraîner un dysfonctionnement global.

L’une des bonnes pratiques consiste à créer une méthodesetupetteardownà chaque lancement de scénario. Ainsi, il est fortement conseillé de favoriser le setup et le teardown au niveau des méthodes.

def setup_method(self, method):
    """ fonction appelée lors du lancement d'un test unitaire faisant partie d'une classe """

def teardown_method(self, method):
    """ fonction appelée à la fin d'un test unitaire faisant partie d'une classe """

Setup / teardown au niveau d’une fonction

Les méthodes pour les fonctions ne sont pas vraiment utiles pour les classes de tests, mais il est intéressant de les connaître si vous n’utilisez pas une classe de test dans vos scénarios. Les méthodessetup_functionetteardown_functionont le même rôle que les deux méthodes précédentes pour les méthodes de classe, mais cette fois pour les fonctions.

def setup_function(function):
    """ fonction appelée lors du lancement d'un test unitaire dans une fonction """

def teardown_function(function):
    """ fonction appelée à la fin d'un test unitaire dans une fonction """

Voyons tout de suite un code qui permet d’illustrer le lancement des différentes méthodes setup et teardown à l’aide de prints. Prenons le code suivant et voyons la sortie des prints dans le terminal, vous verrez ainsi l’ordre d’appel de chaque fonction :

def setup_module(module):
    print("\n--> Setup module")

def teardown_module(module):
    print("--> Teardown module")

class TestClass:
    @classmethod
    def setup_class(cls):
        print("--> Setup class")

    @classmethod
    def teardown_class(cls):
        print("--> Teardown class")

    def setup_method(self, method):
        print("--> Setup method")

    def teardown_method(self, method):
        print("\n--> Teardown method")

    def test_one(self):
        print("--> Run first test")

    def test_two(self):
        print("--> Run second test")

Voici la sortie du terminal après avoir lancé la commande suivante, pytest -s  :

Les prints nous montre l’ordre des lancements de chaque fonction.
Les différents niveaux de setup et teardown

Comme vous pouvez le voir sur le screenshot ci-dessus, il vous est possible de confirmer par vous-même que ces méthodes fonctionnent bien et sont lancées dans un ordre bien précis. N’hésitez pas à reprendre le code et à faire de nouveau cette manipulation pour bien comprendre les différentes méthodes desetupetteardown.

À vous de jouer !

Vous pouvez reprendre le projet OC-commerce avec le framework Django.

Votre mission :

  • Organiser vos tests en classes.

Vous allez voir qu'au niveau des contextes et de la gestion de la base de données, ça sera plus simple avec les classes.

Retrouvez une proposition de correction sur GitHub

En résumé

  • L’utilisation d’une classe de test permettra de mieux organiser et factoriser les tests du projet.

  • Les classes de tests permettent de créer des méthodes afin de minimiser la duplication de code.

  • Il est important de nommer votre test avec le préfixetest_ou le suffixe_test, sinon il ne sera pas exécuté par Pytest.

  • Les méthodessetup()etteardown()permettent de gérer le contexte au début et à la fin du test, cela à différents niveaux : 

    • d’un module ;

    • d’une classe ;

    • d’une méthode ;

    • d’une fonction. 

Avec ce chapitre, nous avons vu l’ensemble des concepts liés aux tests unitaires. Dans le chapitre suivant, nous allons voir un dernier point qui permet de vérifier et d'évaluer la qualité de notre suite de tests – surtout de savoir si nous avons fait du bon travail !

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