26 août 2016

Appliquer le TDD avec pytest

Design & Code

Mouhcine

Amira

Image computer.

26 août 2016

Appliquer le TDD avec pytest

Design & Code

Mouhcine

Amira

Image computer.

26 août 2016

Appliquer le TDD avec pytest

Design & Code

Mouhcine

Amira

Image computer.

Le TDD (Test Driven Development) est un process de développement logiciel qui consiste à faire évoluer un programme petit à petit à travers des mini-cycles : le développeur commence par implémenter un test automatique échoué représentant un cas d’utilisation particulier d’un nouveau code qu’il souhaite ajouter, et modifie ensuite le programme pour passer ce test. Puis, il implémente un deuxième test échoué d’un autre cas particulier de la même partie du code, et modifie le programme pour réussir les deux tests, et ainsi de suite jusqu’à l’obtention d’un programme qui passe plusieurs tests couvrant tous les cas d’utilisation du nouveau code. Ces tests sont ensuite conservés et lancés après toute modification du programme, afin de garantir la non-régression de toutes les fonctions de son code.

Application du TDD avec Pytest

Imaginons qu’on souhaite implémenter un programme en python qui nous permet de valider une adresse email et en suivant le TDD avec l’aide du framework pytest. On n’utilisera pas les expressions régulières pour plus de lisibilité.

Dans notre cas, pour qu’une adresse mail soit valide il faut qu’elle ait la forme suivante : xxx@yyy.zzz Avec:

  1. xxx est une chaine de caractère composée par des lettres et des chiffres et de taille minimale 1.

  2. yyy et zzz sont de même que xxx mais de taille minimale 2.

 Commençons par installer pytest avec la commande :

Après cela, créons un répertoire qui contiendra notre programme :

Ensuite, on crée un fichier pour le code et un autre pour les tests :

$ touch __init__

Commençons par définir une fonction de validation vide dans le fichier email_validation.py


Pour suivre les principes du TDD, on doit d’abord commencer par implémenter un test de la fonction validate_email, un premier cas de test peut être que la chaine “test@test.test” est bien une adresse valide :


Dans le fichier test_email_validation.py on peut implémenter le test comme suit :


Exécutons le test:

$pytest
============================= test session starts =============================
platform win32 -- Python 3.6.5, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
rootdir: email_validation
collected 1 item
test_email_validation.py F                                               [100%]
================================== FAILURES ===================================
______________________ test_validate_email_simple_string ______________________
    def test_validate_email_simple_string():
>       assert validate_email("test@test.test")

NB: pytest lance les fonctions préfixées par “test_” dans les fichiers préfixés par “test_”. Pour en savoir plus sur ces conventions aller sur ce lien

Comme décrit ci-dessus, on doit modifier le code le moins possible pour faire passer ce test.

Par exemple:


Maintenant si on lance pytest on obtient :

$pytest
============================= test session starts =============================
platform win32 -- Python 3.6.5, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
rootdir: email_validation
collected 1 item
test_email_validation.py .                                               [100%]

Et voilà ! On a terminé notre premier mini-cycle.

Recommençons la même démarche pour un deuxième tes. Ici, par exemple on rajoute le test qui vérifie que la chaine “abcd” n’est pas une adresse valide :


Et on lance pytest:

$ pytest
============================= test session starts =============================
platform win32 -- Python 3.6.5, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
rootdir: email_validation
collected 2 items
test_email_validation.py .F                                              [100%]
=================================FAILURES==================================
______________________ test_validate_email_simple_string ______________________
    def test_validate_email_simple_string():
>       assert not validate_email("abcd")

Pytest indique qu’un test passe et un test échoue, on appelle ce stade de cycle l’étape rouge.

La prochaine étape et de modifier le code pour faire passer les deux tests. Par exemple vérifier que la chaine passer en paramètre contient un caractère ‘@’ :



Maintenant les deux tests passent :

$ pytest
============================= test session starts =============================
platform win32 -- Python 3.6.5, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
rootdir: email_validation
collected 2 items
test_email_validation.py ..                                              [100%]

Troisième mini-cycle:

Test : test@@test.test n’est pas une adresse valide

Ensuite l’étape rouge et puis le nouveau code :


4ème mini-cycle :

Test : test@test n’est pas valide

Nouveau code :

def validate_email(address):
    if '@' not in address:
        return False
    parts = address.split('@')
    if len(parts) != 2:
        return False
    local = parts[0]
    domain_name_parts = parts[1]

On répète ensuite les mêmes étapes avec des tests qui couvrent tous les cas d’utilisation de notre programme:


test@test..test N’est pas valide

t@t.t N’est pas valide

t!@t?.t$ N’est pas valide

Enfin notre fichier de tests aura la forme suivante :


Et le fichier du code :

def validate_email(address):
      if '@' not in address:
            return False
      parts = address.split('@')
      if len(parts) != 2:
            return False
      local = parts[0]
      domain_name_parts = parts[1].split(".")
      if len(domain_name_parts) != 2:
            return False
      hostname = domain_name_parts[0]
      domain = domain_name_parts[0]

Quelques fonctionnalités avancées de pytest

1 - Paramétrer un test

Pytest permet de passer plusieurs entrées pour un scenario de test, cela peut se faire comme suit:

@pytest.mark.parametrize("a,b,s",[(1,1,2),(3,2,5),(0,3,3),(-1,1,0)]

En lançant le test, on voit bien que 4 scenarios ont été executés :

========================= test session starts =================================
platform win32 -- Python 3.6.5, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
rootdir: tests
collected 4 items 
test_add.py ....                                                         [100%]

2 - Grouper des tests

On peut grouper les tests sous plusieurs groupes en marquant chaque test avec le nom de son groupe et lancer un groupe de test en particulier.

Exemple :


Dans cet exemple on a distingué deux groupes de tests : les test des nombres numbers et les tests des chaines de caractères strings.

Pour lancer les tests des nombres uniquement la commande et la suivante :

$ pytest test_ example.py -m "numbers"                                            
========================= test session starts ==============================
platform win32 -- Python 3.6.5, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
rootdir: tests
collected 4 items / 2 deselected / 2 selected
test_example.py ..                                                    [100%]

3 - Les fixtures

Les fixtures sont des fonctions s’exécutant avant chaque test auquel elles sont appliquées, elles permettent de factoriser du code se répétant plusieurs fois dans les tests.

Exemple:


Quand les tests sont exécutés maintenant la variable threshold sera assignée avant chacun des deux tests :

========================= test session starts =======================
platform win32 -- Python 3.6.5, pytest-5.2.0, py-1.8.0, pluggy-0.13.0
rootdir: tests
collected 2 items
test_example.py ..                                              [100%]

Conclusion

Pour conclure, le TDD est une approche de développement qui consiste à un avancement un lent mais sûr tout au long du projet, cela permet d’optimiser la dette technique et d’éviter les surprises de la production. Plusieurs frameworks aide à suivre cette méthodologie tel que la libraire de tests standard unittest ou encore le framework pytest dont on a détaillé certaines fonctionnalités dans cet article, Il existe encore plus de fonctionnalités de pytest que celles citées ci-dessus notament pouvoir sauter des tests (skip), executer les tests en parallèle, arrêter l’exécution des tests après un nombre d’echecs donné ...

Retrouvez l'ensemble de nos articles Python dans la catégorie dédiée. Et si vous souhaitez apprendre de nouvelles méthodologies de développement, retrouvez nos articles Agilité & Craft.