Pytest - Guide rapide

Pytest est un framework de test basé sur python, utilisé pour écrire et exécuter des codes de test. À l'heure actuelle des services REST, pytest est principalement utilisé pour les tests d'API même si nous pouvons utiliser pytest pour écrire des tests simples à complexes, c'est-à-dire que nous pouvons écrire des codes pour tester l'API, la base de données, l'interface utilisateur, etc.

Avantages de Pytest

Les avantages de Pytest sont les suivants -

  • Pytest peut exécuter plusieurs tests en parallèle, ce qui réduit le temps d'exécution de la suite de tests.

  • Pytest a sa propre façon de détecter automatiquement le fichier de test et les fonctions de test, s'il n'est pas mentionné explicitement.

  • Pytest nous permet de sauter un sous-ensemble des tests lors de l'exécution.

  • Pytest nous permet d'exécuter un sous-ensemble de toute la suite de tests.

  • Pytest est gratuit et open source.

  • En raison de sa syntaxe simple, pytest est très simple à utiliser.

Dans ce didacticiel, nous expliquerons les principes de base de pytest avec des exemples de programmes.

Dans ce chapitre, nous allons apprendre comment installer pytest.

Pour démarrer l'installation, exécutez la commande suivante -

pip install pytest == 2.9.1

Nous pouvons installer n'importe quelle version de pytest. Ici, 2.9.1 est la version que nous installons.

Pour installer la dernière version de pytest, exécutez la commande suivante -

pip install pytest

Confirmez l'installation à l'aide de la commande suivante pour afficher la section d'aide de pytest.

pytest -h

Lancer pytest sans mentionner de nom de fichier exécutera tous les fichiers de format test_*.py ou *_test.pydans le répertoire courant et les sous-répertoires. Pytest identifie automatiquement ces fichiers en tant que fichiers de test. nouscan faire exécuter par pytest d'autres noms de fichiers en les mentionnant explicitement.

Pytest nécessite que les noms des fonctions de test commencent par test. Noms de fonctions qui ne sont pas au formattest*ne sont pas considérées comme des fonctions de test par pytest. nouscannot faire explicitement que pytest considère toute fonction ne commençant pas par test comme fonction de test.

Nous comprendrons l'exécution des tests dans nos chapitres suivants.

Maintenant, nous allons commencer avec notre premier programme pytest. Nous allons d'abord créer un répertoire et ainsi créer nos fichiers de test dans le répertoire.

Laissez-nous suivre les étapes ci-dessous -

  • Créez un nouveau répertoire nommé automation et accédez au répertoire de votre ligne de commande.

  • Créez un fichier nommé test_square.py et ajoutez le code ci-dessous à ce fichier.

import math

def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

def testsquare():
   num = 7
   assert 7*7 == 40

def tesequality():
   assert 10 == 11

Exécutez le test à l'aide de la commande suivante -

pytest

La commande ci-dessus générera la sortie suivante -

test_square.py .F
============================================== FAILURES 
==============================================
______________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num=7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.06 seconds 
=================================

Voir la première ligne du résultat. Il affiche le nom du fichier et les résultats. F représente un échec de test et le point (.) Représente un succès de test.

En dessous, nous pouvons voir les détails des tests qui ont échoué. Il montrera à quelle déclaration le test a échoué. Dans notre exemple, 7 * 7 est comparé pour l'égalité à 40, ce qui est faux. En fin de compte, nous pouvons voir le résumé de l'exécution du test, 1 échoué et 1 réussi.

La fonction tesequality n'est pas exécutée car pytest ne la considérera pas comme un test car son nom n'est pas au format test*.

Maintenant, exécutez la commande ci-dessous et voyez à nouveau le résultat -

pytest -v

-v augmente la verbosité.

test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
============================================== FAILURES 
==============================================
_____________________________________________ testsquare 
_____________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40
test_square.py:9: AssertionError
================================= 1 failed, 1 passed in 0.04 seconds 
=================================

Maintenant, le résultat est plus explicatif sur le test qui a échoué et le test qui a réussi.

Note - la commande pytest exécutera tous les fichiers de format test_* ou *_test dans le répertoire courant et les sous-répertoires.

Dans ce chapitre, nous allons apprendre à exécuter un seul fichier de test et plusieurs fichiers de test. Nous avons déjà un fichier de testtest_square.pyétabli. Créer un nouveau fichier de testtest_compare.py avec le code suivant -

def test_greater():
   num = 100
   assert num > 100

def test_greater_equal():
   num = 100
   assert num >= 100

def test_less():
   num = 100
   assert num < 200

Maintenant, pour exécuter tous les tests à partir de tous les fichiers (2 fichiers ici), nous devons exécuter la commande suivante -

pytest -v

La commande ci-dessus exécutera des tests à la fois test_square.py et test_compare.py. La sortie sera générée comme suit -

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
test_square.py::test_sqrt PASSED
test_square.py::testsquare FAILED
================================================ FAILURES 
================================================
______________________________________________ test_greater 
______________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100

test_compare.py:3: AssertionError
_______________________________________________ testsquare 
_______________________________________________
   def testsquare():
   num = 7
>  assert 7*7 == 40
E  assert (7 * 7) == 40

test_square.py:9: AssertionError
=================================== 2 failed, 3 passed in 0.07 seconds 
===================================

Pour exécuter les tests à partir d'un fichier spécifique, utilisez la syntaxe suivante -

pytest <filename> -v

Maintenant, exécutez la commande suivante -

pytest test_compare.py -v

La commande ci-dessus exécutera les tests uniquement à partir du fichier test_compare.py. Notre résultat sera -

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
test_compare.py::test_less PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
   def test_greater():
   num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
================================= 1 failed, 2 passed in 0.04 seconds 
=================================

Dans un scénario réel, nous aurons plusieurs fichiers de test et chaque fichier aura un certain nombre de tests. Les tests couvriront divers modules et fonctionnalités. Supposons que nous souhaitons exécuter uniquement un ensemble spécifique de tests; comment s'y prendre?

Pytest propose deux méthodes pour exécuter le sous-ensemble de la suite de tests.

  • Sélectionnez les tests à exécuter en fonction de la correspondance des sous-chaînes des noms de test.
  • Sélectionnez les groupes de tests à exécuter en fonction des marqueurs appliqués.

Nous expliquerons ces deux avec des exemples dans les chapitres suivants.

Pour exécuter les tests contenant une chaîne dans son nom, nous pouvons utiliser la syntaxe suivante -

pytest -k <substring> -v

-k <substring> représente la sous-chaîne à rechercher dans les noms de test.

Maintenant, exécutez la commande suivante -

pytest -k great -v

Cela exécutera tous les noms de test ayant le mot ‘great’en son nom. Dans ce cas, ils sonttest_greater() et test_greater_equal(). Voir le résultat ci-dessous.

test_compare.py::test_greater FAILED
test_compare.py::test_greater_equal PASSED
============================================== FAILURES 
==============================================
____________________________________________ test_greater 
____________________________________________
def test_greater():
num = 100
>  assert num > 100
E  assert 100 > 100
test_compare.py:3: AssertionError
========================== 1 failed, 1 passed, 3 deselected in 0.07 seconds 
==========================

Ici dans le résultat, nous pouvons voir 3 tests désélectionnés. En effet, ces noms de test ne contiennent pas le motgreat en eux.

Note - Le nom de la fonction de test doit toujours commencer par «test».

Dans ce chapitre, nous allons apprendre à regrouper les tests à l'aide de marqueurs.

Pytest nous permet d'utiliser des marqueurs sur les fonctions de test. Les marqueurs sont utilisés pour définir diverses caractéristiques / attributs pour tester les fonctions. Pytest fournit de nombreux marqueurs intégrés tels que xfail, skip et parametrize. En dehors de cela, les utilisateurs peuvent créer leurs propres noms de marqueurs. Les marqueurs sont appliqués sur les tests en utilisant la syntaxe donnée ci-dessous -

@pytest.mark.<markername>

Pour utiliser des marqueurs, nous devons import pytestmodule dans le fichier de test. Nous pouvons définir nos propres noms de marqueurs pour les tests et exécuter les tests avec ces noms de marqueurs.

Pour exécuter les tests marqués, nous pouvons utiliser la syntaxe suivante -

pytest -m <markername> -v

-m <nom_marque> représente le nom du marqueur des tests à exécuter.

Mettez à jour nos fichiers de test test_compare.py et test_square.pyavec le code suivant. Nous définissons 3 marqueurs– great, square, others.

test_compare.py

import pytest
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

test_square.py

import pytest
import math

@pytest.mark.square
def test_sqrt():
   num = 25
   assert math.sqrt(num) == 5

@pytest.mark.square
def testsquare():
   num = 7
   assert 7*7 == 40

@pytest.mark.others
   def test_equality():
   assert 10 == 11

Maintenant, pour exécuter les tests marqués comme others, exécutez la commande suivante -

pytest -m others -v

Voir le résultat ci-dessous. Il a exécuté les 2 tests marqués commeothers.

test_compare.py::test_less PASSED
test_square.py::test_equality FAILED
============================================== FAILURES
==============================================
___________________________________________ test_equality
____________________________________________
   @pytest.mark.others
   def test_equality():
>  assert 10 == 11
E  assert 10 == 11
test_square.py:16: AssertionError
========================== 1 failed, 1 passed, 4 deselected in 0.08 seconds
==========================

De même, nous pouvons également exécuter des tests avec d'autres marqueurs - excellent, comparez

Les fixtures sont des fonctions qui s'exécuteront avant chaque fonction de test à laquelle elles sont appliquées. Les fixtures sont utilisées pour fournir des données aux tests telles que les connexions à la base de données, les URL à tester et certaines sortes de données d'entrée. Par conséquent, au lieu d'exécuter le même code pour chaque test, nous pouvons attacher la fonction fixture aux tests et elle exécutera et retournera les données au test avant d'exécuter chaque test.

Une fonction est marquée comme un appareil par -

@pytest.fixture

Une fonction de test peut utiliser un appareil en mentionnant le nom de l'appareil en tant que paramètre d'entrée.

Créer un fichier test_div_by_3_6.py et ajoutez-y le code ci-dessous

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Ici, nous avons une fonction fixture nommée input_value, qui fournit l'entrée aux tests. Pour accéder à la fonction fixture, les tests doivent mentionner le nom du fixture comme paramètre d'entrée.

Pytest pendant l'exécution du test, verra le nom de l'appareil comme paramètre d'entrée. Il exécute ensuite la fonction fixture et la valeur renvoyée est stockée dans le paramètre d'entrée, qui peut être utilisé par le test.

Exécutez le test à l'aide de la commande suivante -

pytest -k divisible -v

La commande ci-dessus générera le résultat suivant -

test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:12: AssertionError
========================== 1 failed, 1 passed, 6 deselected in 0.07 seconds
==========================

Cependant, l'approche a ses propres limites. Une fonction fixe définie dans un fichier de test a une portée dans le fichier de test uniquement. Nous ne pouvons pas utiliser cet appareil dans un autre fichier de test. Pour rendre un fixture disponible pour plusieurs fichiers de test, nous devons définir la fonction fixture dans un fichier appelé conftest.py.conftest.py est expliqué dans le chapitre suivant.

Nous pouvons définir les fonctions des appareils dans ce fichier pour les rendre accessibles à travers plusieurs fichiers de test.

Créer un nouveau fichier conftest.py et ajoutez-y le code ci-dessous -

import pytest

@pytest.fixture
def input_value():
   input = 39
   return input

Modifier le test_div_by_3_6.py pour supprimer la fonction de fixation -

import pytest

def test_divisible_by_3(input_value):
   assert input_value % 3 == 0

def test_divisible_by_6(input_value):
   assert input_value % 6 == 0

Créer un nouveau fichier test_div_by_13.py -

import pytest

def test_divisible_by_13(input_value):
   assert input_value % 13 == 0

Maintenant, nous avons les fichiers test_div_by_3_6.py et test_div_by_13.py en utilisant le dispositif défini dans conftest.py.

Exécutez les tests en exécutant la commande suivante -

pytest -k divisible -v

La commande ci-dessus générera le résultat suivant -

test_div_by_13.py::test_divisible_by_13 PASSED
test_div_by_3_6.py::test_divisible_by_3 PASSED
test_div_by_3_6.py::test_divisible_by_6 FAILED
============================================== FAILURES
==============================================
________________________________________ test_divisible_by_6
_________________________________________
input_value = 39
   def test_divisible_by_6(input_value):
>  assert input_value % 6 == 0
E  assert (39 % 6) == 0
test_div_by_3_6.py:7: AssertionError
========================== 1 failed, 2 passed, 6 deselected in 0.09 seconds
==========================

Les tests chercheront un appareil dans le même fichier. Comme le luminaire n'est pas trouvé dans le fichier, il vérifiera le montage dans le fichier conftest.py. Lorsqu'il le trouve, la méthode fixture est appelée et le résultat est renvoyé à l'argument d'entrée du test.

Le paramétrage d'un test est effectué pour exécuter le test sur plusieurs ensembles d'entrées. Nous pouvons le faire en utilisant le marqueur suivant -

@pytest.mark.parametrize

Copiez le code ci-dessous dans un fichier appelé test_multiplication.py -

import pytest

@pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
def test_multiplication_11(num, output):
   assert 11*num == output

Ici, le test multiplie une entrée par 11 et compare le résultat avec la sortie attendue. Le test comporte 4 ensembles d'entrées, chacune a 2 valeurs - l'une est le nombre à multiplier par 11 et l'autre est le résultat attendu.

Exécutez le test en exécutant la commande suivante -

Pytest -k multiplication -v

La commande ci-dessus générera la sortie suivante -

test_multiplication.py::test_multiplication_11[1-11] PASSED
test_multiplication.py::test_multiplication_11[2-22] PASSED
test_multiplication.py::test_multiplication_11[3-35] FAILED
test_multiplication.py::test_multiplication_11[4-44] PASSED
============================================== FAILURES
==============================================
_________________ test_multiplication_11[3-35] __________________
num = 3, output = 35
   @pytest.mark.parametrize("num, output",[(1,11),(2,22),(3,35),(4,44)])
   def test_multiplication_11(num, output):
>  assert 11*num == output
E  assert (11 * 3) == 35
test_multiplication.py:5: AssertionError
============================== 1 failed, 3 passed, 8 deselected in 0.08 seconds
==============================

Dans ce chapitre, nous découvrirons les tests Skip et Xfail dans Pytest.

Maintenant, considérez les situations ci-dessous -

  • Un test n'est pas pertinent pendant un certain temps pour certaines raisons.
  • Une nouvelle fonctionnalité est en cours d'implémentation et nous avons déjà ajouté un test pour cette fonctionnalité.

Dans ces situations, nous avons la possibilité de xfail le test ou de sauter les tests.

Pytest exécutera le test xfailed, mais il ne sera pas considéré comme une partie des tests ayant échoué ou réussi. Les détails de ces tests ne seront pas imprimés même si le test échoue (rappelez-vous que pytest imprime généralement les détails du test échoué). Nous pouvons xfail tests en utilisant le marqueur suivant -

@pytest.mark.xfail

Sauter un test signifie que le test ne sera pas exécuté. Nous pouvons sauter les tests en utilisant le marqueur suivant -

@pytest.mark.skip

Plus tard, lorsque le test devient pertinent, nous pouvons supprimer les marqueurs.

Modifier le test_compare.py nous devons déjà inclure les marqueurs xfail et skip -

import pytest
@pytest.mark.xfail
@pytest.mark.great
def test_greater():
   num = 100
   assert num > 100

@pytest.mark.xfail
@pytest.mark.great
def test_greater_equal():
   num = 100
   assert num >= 100

@pytest.mark.skip
@pytest.mark.others
def test_less():
   num = 100
   assert num < 200

Exécutez le test à l'aide de la commande suivante -

pytest test_compare.py -v

Lors de l'exécution, la commande ci-dessus générera le résultat suivant -

test_compare.py::test_greater xfail
test_compare.py::test_greater_equal XPASS
test_compare.py::test_less SKIPPED
============================ 1 skipped, 1 xfailed, 1 xpassed in 0.06 seconds
============================

Dans un scénario réel, une fois qu'une nouvelle version du code est prête à être déployée, elle est d'abord déployée dans un environnement de pré-production / préparation. Ensuite, une suite de tests s'exécute dessus.

Le code est qualifié pour un déploiement en production uniquement si la suite de tests réussit. En cas d'échec du test, qu'il s'agisse d'un ou plusieurs, le code n'est pas prêt pour la production.

Par conséquent, que se passe-t-il si nous voulons arrêter l'exécution de la suite de tests peu de temps après l'échec de n nombre de tests. Cela peut être fait dans pytest en utilisant maxfail.

La syntaxe pour arrêter l'exécution de la suite de tests peu après n nombre d'échecs de test est la suivante -

pytest --maxfail = <num>

Créez un fichier test_failure.py avec le code suivant.

import pytest
import math

def test_sqrt_failure():
   num = 25
   assert math.sqrt(num) == 6

def test_square_failure():
   num = 7
   assert 7*7 == 40

def test_equality_failure():
   assert 10 == 11

Tous les 3 tests échoueront lors de l'exécution de ce fichier de test. Ici, nous allons arrêter l'exécution du test après un échec lui-même en -

pytest test_failure.py -v --maxfail = 1
test_failure.py::test_sqrt_failure FAILED
=================================== FAILURES
=================================== _______________________________________
test_sqrt_failure __________________________________________
   def test_sqrt_failure():
   num = 25
>  assert math.sqrt(num) == 6
E  assert 5.0 == 6
E  + where 5.0 = <built-in function sqrt>(25)
E  + where <built-in function sqrt>= math.sqrt
test_failure.py:6: AssertionError
=============================== 1 failed in 0.04 seconds
===============================

Dans le résultat ci-dessus, nous pouvons voir que l'exécution est arrêtée sur un échec.

Par défaut, pytest exécute les tests dans un ordre séquentiel. Dans un scénario réel, une suite de tests aura un certain nombre de fichiers de test et chaque fichier aura un tas de tests. Cela conduira à un temps d'exécution important. Pour surmonter cela, pytest nous offre une option pour exécuter des tests en parallèle.

Pour cela, nous devons d'abord installer le plugin pytest-xdist.

Installez pytest-xdist en exécutant la commande suivante -

pip install pytest-xdist

Maintenant, nous pouvons exécuter des tests en utilisant la syntaxe pytest -n <num>

pytest -n 3

-n <num> exécute les tests en utilisant plusieurs nœuds de calcul, ici c'est 3.

Nous n'aurons pas beaucoup de décalage horaire s'il n'y a que quelques tests à exécuter. Cependant, il est important que la suite de tests soit volumineuse.

Nous pouvons générer les détails de l'exécution du test dans un fichier xml. Ce fichier xml est principalement utile dans les cas où nous avons un tableau de bord qui projette les résultats des tests. Dans de tels cas, le xml peut être analysé pour obtenir les détails de l'exécution.

Nous allons maintenant exécuter les tests de test_multiplcation.py et générer le xml en exécutant

pytest test_multiplication.py -v --junitxml="result.xml"

Nous pouvons maintenant voir que result.xml est généré avec les données suivantes -

<?xml version = "1.0" encoding = "utf-8"?>
<testsuite errors = "0" failures = "1"
name = "pytest" skips = "0" tests = "4" time = "0.061">
   <testcase classname = "test_multiplication"          
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[1-11]"
      time = "0.00117516517639>
   </testcase>
   
   <testcase classname = "test_multiplication"    
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[2-22]"
      time = "0.00155973434448">
   </testcase>

   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[3-35]" time = "0.00144290924072">
      failure message = "assert (11 * 3) == 35">num = 3, output = 35

         @pytest.mark.parametrize("num,
         output",[(1,11),(2,22),(3,35),(4,44)])
            
         def test_multiplication_11(num, output):> 
         assert 11*num == output
         E assert (11 * 3) == 35

         test_multiplication.py:5: AssertionErro
      </failure>
   </testcase>
   <testcase classname = "test_multiplication" 
      file = "test_multiplication.py"
      line = "2" name = "test_multiplication_11[4-44]"
      time = "0.000945091247559">
   </testcase>
</testsuite>

Ici, le tag <testsuit> résume qu'il y a eu 4 tests et le nombre d'échecs est de 1.

  • Le tag <testcase> donne les détails de chaque test exécuté.

  • La balise <failure> donne les détails du code de test ayant échoué.

Dans ce tutoriel pytest, nous avons couvert les domaines suivants -

  • Installation de pytest ..
  • Identification des fichiers de test et des fonctions de test.
  • Exécution de tous les fichiers de test à l'aide de pytest –v.
  • Exécution d'un fichier spécifique en utilisant pytest <filename> -v.
  • Exécutez les tests par sous-chaîne correspondant à pytest -k <substring> -v.
  • Exécutez des tests basés sur les marqueurs pytest -m <nom_marqueur> -v.
  • Créer des appareils avec @ pytest.fixture.
  • conftest.py permet d'accéder aux appareils à partir de plusieurs fichiers.
  • Paramétrer les tests avec @ pytest.mark.parametrize.
  • Xfailing tests en utilisant @ pytest.mark.xfail.
  • Ignorer les tests avec @ pytest.mark.skip.
  • Arrêtez l'exécution du test sur n échecs en utilisant pytest --maxfail = <num>.
  • Exécution de tests en parallèle à l'aide de pytest -n <num>.
  • Génération de résultats xml en utilisant pytest -v --junitxml = "result.xml".

Ce tutoriel vous a présenté le framework pytest. Vous devriez maintenant pouvoir commencer à écrire des tests à l'aide de pytest.

En tant que bonne pratique -

  • Créez différents fichiers de test en fonction de la fonctionnalité / du module testé.
  • Donnez des noms significatifs aux fichiers et méthodes de test.
  • Ayez suffisamment de marqueurs pour regrouper les tests en fonction de divers critères.
  • Utilisez des luminaires chaque fois que nécessaire.