Tutoriel PyTest : Qu’est-ce que PyTest ? (avec exemples)
⚡ Résumé intelligent
Pytest est un Python Framework de test facilitant la création de tests simples et évolutifs pour les bases de données, les API et les interfaces utilisateur. Il prend en charge les fixtures, la paramétrisation, les marqueurs, les exécutions parallèles et la génération de rapports d'assertions détaillés, des tests unitaires de base aux tests fonctionnels complexes.

Pytest est l'un des frameworks de test les plus utilisés dans le Python Cet écosystème permet de conserver un code de test concis et lisible, détecte automatiquement les tests et évolue d'une première assertion vers une suite de tests complète pour les bases de données, les API et les interfaces utilisateur. Les sections suivantes détaillent l'installation, les assertions, les fixtures, les marqueurs, la paramétrisation et les exécutions parallèles à l'aide d'exemples pratiques.
Qu'est-ce que Pytest ?
Pytest est un framework de test qui vous permet d'écrire du code de test en utilisant Python Pytest est un langage de programmation qui permet de créer des cas de test simples et évolutifs pour les bases de données, les API et même les interfaces utilisateur. Principalement utilisé pour les tests d'API, il s'adapte aussi bien aux tests unitaires simples qu'aux tests fonctionnels complexes.
Pourquoi utiliser Pytest ?
Voici quelques avantages de pytest :
- Très facile à prendre en main, grâce à sa syntaxe simple et claire.
- Peut exécuter des tests en parallèle.
- Il est possible d'exécuter un test spécifique ou un sous-ensemble de tests.
- Détecte automatiquement les tests.
- Possibilité de passer les tests.
- Open source.
Comment installer Pytest
Voici la procédure d'installation de pytest :
Étape 1) Vous pouvez installer pytest avec la commande ci-dessous.
pip install pytest==2.9.1
Une fois l'installation terminée, vous pouvez la confirmer avec la commande suivante.
py.test -h
Ceci affichera l'aide.
Premier exemple de base avec Pytest
Vous allez maintenant apprendre à utiliser pytest avec un exemple pytest de base.
Créez un dossier nommé study_pytest. Vous y créerez vos fichiers de test. Veuillez vous placer dans ce dossier via votre terminal. Créez ensuite un fichier nommé test_sample1.py à l'intérieur.
Ajoutez-y le code ci-dessous et enregistrez.
import pytest def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed" def test_file1_method2(): x=5 y=6 assert x+1 == y,"test failed"
Exécutez le test à l'aide de la commande ci-dessous.
py.test
Vous obtiendrez le résultat suivant.
test_sample1.py F.
============================================== FAILURES ========================================
____________________________________________ test_sample1 ______________________________________
def test_file1_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed"
E AssertionError: test failed
E assert 5 == 6
test_sample1.py:6: AssertionError
Dans le fichier test_sample1.py, la lettre F indique un échec et le point (.) une réussite. La section relative aux échecs affiche la ou les méthodes ayant échoué ainsi que la ligne correspondante. Ici, x==y signifie 5==6, ce qui est faux.
Ensuite, vous découvrirez les assertions dans pytest.
Assertions dans Pytest
Dans Pytest, les assertions sont des vérifications qui renvoient soit Vrai, soit Faux. Si une assertion échoue dans une méthode de test, l'exécution de cette méthode est interrompue. Le reste du code n'est pas exécuté et Pytest passe à la méthode de test suivante.
Exemples d'assertions Pytest :
assert "hello" == "Hai" is an assertion failure. assert 4==4 is a successful assertion assert True is a successful assertion assert False is an assertion failure.
Considérer ce qui suit.
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Placez ce code dans test_file1_method1() à la place de l'assertion ci-dessous.
assert x == y,"test failed"
L'exécution du test donnera l'échec comme AssertionError : le test a échoué x=5 y=6.
Comment Pytest identifie les fichiers de test et les méthodes de test
Par défaut, pytest n'identifie que les noms de fichiers commençant par tester_ ou se terminant par _tester comme les fichiers de test. Nous pouvons toutefois mentionner explicitement d'autres noms de fichiers (explication ultérieure). Pytest exige que les noms des méthodes de test commencent par testerTous les autres noms de méthodes sont ignorés, même si nous demandons explicitement leur exécution.
Voici quelques exemples de noms de fichiers pytest valides et invalides.
test_login.py - valid login_test.py - valid testlogin.py -invalid logintest.py -invalid
Remarque : Oui, nous pouvons explicitement demander à pytest de choisir testlogin.py et logintest.py.
Voici quelques exemples de méthodes de test pytest valides et invalides.
def test_file1_method1(): - valid def testfile1_method1(): - valid def file1_method1(): - invalid
Remarque : même si nous mentionnons explicitement file1_method1(), pytest n'exécutera pas cette méthode.
Exécuter plusieurs tests à partir d'un fichier spécifique et de plusieurs fichiers
Actuellement, le dossier study_pytest contient un fichier test_sample1.py. Supposons qu'il contienne plusieurs fichiers, par exemple test_sample2.py et test_sample3.py. Pour exécuter tous les tests de tous les fichiers du dossier et de ses sous-dossiers, il suffit d'exécuter la commande pytest.
py.test
Cette commande exécutera tous les noms de fichiers commençant par test_ et se terminant par _test dans ce dossier et ses sous-dossiers.
Pour exécuter des tests uniquement à partir d'un fichier spécifique, nous pouvons utiliser py.test. .
py.test test_sample1.py
Exécuter un sous-ensemble du test complet avec Pytest
Parfois, nous ne souhaitons pas exécuter l'ensemble des tests. Pytest nous permet d'exécuter des tests spécifiques. Nous pouvons procéder de deux manières :
- Grouping de noms de tests par correspondance de sous-chaînes.
- Grouping des tests par marqueurs.
Nous avons déjà le fichier test_sample1.py. Créez un fichier test_sample2.py et ajoutez-y le code ci-dessous.
def test_file2_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) def test_file2_method2(): x=5 y=6 assert x+1 == y,"test failed"
Nous avons donc actuellement la situation suivante.
• test_sample1.py • test_file1_method1() • test_file1_method2() • test_sample2.py • test_file2_method1() • test_file2_method2()
Option 1) Exécuter les tests par correspondance de sous-chaînes
Pour exécuter tous les tests dont le nom contient « method1 », il faut exécuter la commande ci-dessous.
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
L'exécution de la commande py.test -k method1 -v vous donnera le résultat suivant.
test_sample2.py::test_file2_method1 FAILED
test_sample1.py::test_file1_method1 FAILED
============================================== FAILURES ==============================================
_________________________________________ test_file2_method1 _________________________________________
def test_file2_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E AssertionError: test failed because x=5 y=6
E assert 5 == 6
test_sample2.py:5: AssertionError
_________________________________________ test_file1_method1 _________________________________________
@pytest.mark.only
def test_file1_method1():
x=5
y=6
assert x+1 == y,"test failed"
> assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
E AssertionError: test failed because x=5 y=6
E assert 5 == 6
test_sample1.py:8: AssertionError
================================= 2 tests deselected by '-kmethod1' ==================================
=============================== 2 failed, 2 deselected in 0.02 seconds ===============================
Ici, vous pouvez voir, vers la fin, 2 tests désélectionnés par '-kmethod1', qui sont test_file1_method2 et test_file2_method2.
Essayez différentes combinaisons comme celles-ci.
py.test -k method -v - will run all the four methods py.test -k methods -v – will not run any test as there is no test name matches the substring 'methods'
Option 2) Exécuter des tests par marqueurs
Pytest permet de définir différents attributs pour les méthodes de test grâce aux marqueurs `@pytest.mark`. Pour utiliser ces marqueurs dans un fichier de test, il faut importer pytest. Nous allons ici appliquer différents noms de marqueurs aux méthodes de test et exécuter des tests spécifiques en fonction de ces noms. Voici comment définir les marqueurs pour chaque méthode de test.
@pytest.mark.<name>.
Nous définissons les marqueurs set1 et set2 sur les méthodes de test, et nous exécuterons le test en utilisant les noms de ces marqueurs. Mettez à jour les fichiers de test avec le code suivant.
test_sample1.py
import pytest @pytest.mark.set1 def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) @pytest.mark.set2 def test_file1_method2(): x=5 y=6 assert x+1 == y,"test failed"
test_sample2.py
import pytest @pytest.mark.set1 def test_file2_method1(): x=5 y=6 assert x+1 == y,"test failed" assert x == y,"test failed because x=" + str(x) + " y=" + str(y) @pytest.mark.set1 def test_file2_method2(): x=5 y=6 assert x+1 == y,"test failed"
Nous pouvons exécuter le test marqué avec la commande ci-dessous.
py.test -m <name> -m <name> mentions the marker name
Exécutez py.test -m set1. Cela exécutera les méthodes test_file1_method1, test_file2_method1 et test_file2_method2. L'exécution de py.test -m set2 exécutera test_file1_method2.
Exécuter les tests en parallèle avec Pytest
En général, une suite de tests comprend plusieurs fichiers de test et des centaines de méthodes de test, ce qui peut prendre un temps considérable à l'exécution. Pytest permet d'exécuter les tests en parallèle. Pour cela, il faut d'abord installer pytest-xdist en exécutant la commande ci-dessous.
pip install pytest-xdist
Vous pouvez maintenant exécuter les tests avec la commande suivante.
py.test -n 4
Ici, -n Cette commande exécute les tests à l'aide de plusieurs processus. Dans l'exemple ci-dessus, quatre processus seront utilisés pour exécuter les tests.
Agencements Pytest
Les fixtures sont utilisées lorsqu'on souhaite exécuter du code avant chaque méthode de test. Ainsi, au lieu de répéter le même code dans chaque test, on définit des fixtures. Généralement, les fixtures servent à initialiser les connexions à la base de données et à transmettre les variables de base. URL, et ainsi de suite. Une méthode est marquée comme fixture pytest en la marquant comme suit.
@pytest.fixture
Une méthode de test peut utiliser un fixture pytest en le spécifiant comme paramètre d'entrée. Créez un nouveau fichier test_basic_fixture.py avec le code suivant.
import pytest @pytest.fixture def supply_AA_BB_CC(): aa=25 bb =35 cc=45 return [aa,bb,cc] def test_comparewithAA(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
Ici:
- Nous avons un dispositif nommé supply_AA_BB_CC. Cette méthode renvoie une liste de trois valeurs.
- Nous disposons de trois méthodes de test comparant chacune des valeurs.
Chaque fonction de test possède un argument d'entrée dont le nom correspond à une fixture disponible. Pytest appelle ensuite la méthode de la fixture correspondante et stocke les valeurs renvoyées dans l'argument d'entrée, ici la liste [25, 35, 45]. Les éléments de cette liste sont ensuite utilisés dans les méthodes de test pour la comparaison. Exécutez maintenant le test et observez le résultat.
py.test test_basic_fixture
test_basic_fixture.py::test_comparewithAA FAILED
test_basic_fixture.py::test_comparewithBB PASSED
test_basic_fixture.py::test_comparewithCC FAILED
============================================== FAILURES ==============================================
_________________________________________ test_comparewithAA _________________________________________
supply_AA_BB_CC = [25, 35, 45]
def test_comparewithAA(supply_AA_BB_CC):
zz=35
> assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed"
E AssertionError: aa and zz comparison failed
E assert 25 == 35
test_basic_fixture.py:10: AssertionError
_________________________________________ test_comparewithCC _________________________________________
supply_AA_BB_CC = [25, 35, 45]
def test_comparewithCC(supply_AA_BB_CC):
zz=35
> assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
E AssertionError: cc and zz comparison failed
E assert 45 == 35
test_basic_fixture.py:16: AssertionError
================================= 2 failed, 1 passed in 0.05 seconds =================================
Le test test_comparewithBB réussit, puisque zz=BB=35, et les deux autres tests échouent.
La méthode de configuration a une portée limitée au fichier de test où elle est définie. Si l'on tente d'accéder à cette configuration depuis un autre fichier de test, une erreur indiquant que la configuration est introuvable sera générée. 'supply_AA_BB_CC' introuvable pour les méthodes de test dans les autres fichiers.
Pour utiliser la même fixture avec plusieurs fichiers de test, nous créons des méthodes de fixture dans un fichier nommé conftest.py. Prenons l'exemple suivant avec pytest. Créez trois fichiers : conftest.py, test_basic_fixture.py et test_basic_fixture2.py, avec le code suivant.
concours.py
import pytest @pytest.fixture def supply_AA_BB_CC(): aa=25 bb =35 cc=45 return [aa,bb,cc]
test_basic_fixture.py
import pytest def test_comparewithAA(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC(supply_AA_BB_CC): zz=35 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
test_basic_fixture2.py
import pytest def test_comparewithAA_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[0]==zz,"aa and zz comparison failed" def test_comparewithBB_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[1]==zz,"bb and zz comparison failed" def test_comparewithCC_file2(supply_AA_BB_CC): zz=25 assert supply_AA_BB_CC[2]==zz,"cc and zz comparison failed"
Pytest recherchera d'abord le fichier de test dans le fichier de configuration, et s'il ne le trouve pas, il le cherchera dans le fichier conftest.py. Exécutez le test avec la commande py.test -k test_comparewith -v pour obtenir le résultat ci-dessous.
test_basic_fixture.py::test_comparewithAA FAILED test_basic_fixture.py::test_comparewithBB PASSED test_basic_fixture.py::test_comparewithCC FAILED test_basic_fixture2.py::test_comparewithAA_file2 PASSED test_basic_fixture2.py::test_comparewithBB_file2 FAILED test_basic_fixture2.py::test_comparewithCC_file2 FAILED
Test paramétré Pytest
Paramétrer un test permet de l'exécuter avec plusieurs ensembles d'arguments. On peut le faire avec `@pytest.mark.parametrize`. L'exemple pytest ci-dessous illustre ce principe. Nous allons passer trois arguments à une méthode de test. Cette méthode additionnera les deux premiers arguments et comparera le résultat avec le troisième. Créez le fichier de test `test_addition.py` avec le code suivant.
import pytest @pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)]) def test_add(input1, input2, output): assert input1+input2 == output,"failed"
Ici, la méthode de test accepte trois arguments : input1, input2 et output. Elle additionne input1 et input2 et compare la somme obtenue à la sortie. Exécutons le test avec la commande `py.test -k test_add -v` et observons le résultat.
test_addition.py::test_add[5-5-10] PASSED
test_addition.py::test_add[3-5-12] FAILED
============================================== FAILURES ==============================================
__________________________________________ test_add[3-5-12] __________________________________________
input1 = 3, input2 = 5, output = 12
@pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
def test_add(input1, input2, output):
> assert input1+input2 == output,"failed"
E AssertionError: failed
E assert (3 + 5) == 12
test_addition.py:5: AssertionError
Vous pouvez voir que les tests ont été exécutés deux fois, l'un vérifiant 5+5 ==10 et l'autre vérifiant 3+5 ==12.
test_addition.py::test_add[5-5-10] RÉUSSI
test_addition.py :: test_add [3-5-12] ÉCHEC
Tests Pytest Xfail et Skip
Il y aura des situations où nous ne souhaiterons pas exécuter un test, ou un cas de test n'est pas pertinent à un moment donné. Dans ces situations, nous avons la possibilité de faire échouer le test (xfail) ou de l'ignorer. Le test ayant échoué sera exécuté, mais il ne sera pas comptabilisé parmi les tests réussis ou échoués. Il n'y aura aucun tracUn message d'erreur s'affiche si le test échoue. Nous pouvons marquer les tests comme échoués à l'aide de `@pytest.mark.xfail`. Ignorerping Un test marqué comme non exécuté signifie qu'il ne sera pas exécuté. On peut ignorer des tests grâce à `@pytest.mark.skip`. Modifiez le fichier `test_addition.py` avec le code ci-dessous.
import pytest @pytest.mark.skip def test_add_1(): assert 100+200 == 400,"failed" @pytest.mark.skip def test_add_2(): assert 100+200 == 300,"failed" @pytest.mark.xfail def test_add_3(): assert 15+13 == 28,"failed" @pytest.mark.xfail def test_add_4(): assert 15+13 == 100,"failed" def test_add_5(): assert 3+2 == 5,"failed" def test_add_6(): assert 3+2 == 6,"failed"
Ici:
- test_add_1 et test_add_2 sont ignorés et ne seront pas exécutés.
- Les tests test_add_3 et test_add_4 ont échoué (xfailed). Ils seront exécutés et feront partie des tests ayant échoué (xfailed) ou réussi (xpassed). Il n'y aura aucun autre test. tracretour pour les échecs.
- Les tests test_add_5 et test_add_6 seront exécutés, et test_add_6 signalera un échec avec un tracretour pendant que test_add_5 réussit.
Exécutez le test avec py.test test_addition.py -v et observez le résultat.
test_addition.py::test_add_1 SKIPPED
test_addition.py::test_add_2 SKIPPED
test_addition.py::test_add_3 XPASS
test_addition.py::test_add_4 xfail
test_addition.py::test_add_5 PASSED
test_addition.py::test_add_6 FAILED
============================================== FAILURES ==============================================
_____________________________________________ test_add_6 _____________________________________________
def test_add_6():
> assert 3+2 == 6,"failed"
E AssertionError: failed
E assert (3 + 2) == 6
test_addition.py:24: AssertionError
================ 1 failed, 1 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.07 seconds =================
Résultats au format XML
Nous pouvons générer des résultats de test au format XML, que nous pourrons ensuite transmettre aux serveurs d'intégration continue pour traitement. Pour ce faire, utilisez la commande : `py.test test_sample1.py -v --junitxml="result.xml"`. Le fichier `result.xml` enregistrera le résultat de l'exécution du test. Vous trouverez un exemple de fichier `result.xml` ci-dessous.
<?xml version="1.0" encoding="UTF-8"?> <testsuite errors="0" failures="1" name="pytest" skips="0" tests="2" time="0.046"> <testcase classname="test_sample1" file="test_sample1.py" line="3" name="test_file1_method1" time="0.001384973526"> <failure message="AssertionError:test failed because x=5 y=6 assert 5 ==6"> @pytest.mark.set1 def test_file1_method1(): x=5 y=6 assert x+1 == y,"test failed" > assert x == y,"test failed because x=" + str(x) + " y=" + str(y) E AssertionError: test failed because x=5 y=6 E assert 5 == 6 test_sample1.py:9: AssertionError </failure> </testcase> <testcase classname="test_sample1" file="test_sample1.py" line="10" name="test_file1_method2" time="0.000830173492432" /> </testsuite>
Depuis Nous pouvons voir un total de deux tests, dont un a échoué. Ci-dessous, vous trouverez les détails relatifs à chaque test exécuté. étiqueter.
Pytest Framework pour tester une API
Nous allons maintenant créer un petit framework pytest pour tester une API. L'API utilisée ici est une API gratuite. reqres.inCe site web fournit simplement une API testable et ne stocke aucune donnée. Nous allons rédiger ici quelques tests pour les points suivants :
- Liste de quelques utilisateurs.
- Se connecter avec les utilisateurs.
Créez les fichiers ci-dessous avec le code fourni. Tout d'abord, conftest.py contient un fixture qui fournira la base URL pour toutes les méthodes de test.
import pytest @pytest.fixture def supply_url(): return "https://reqres.in/api"
Ensuite, le fichier test_list_user.py contient les méthodes de test permettant de lister les utilisateurs valides et invalides.
- test_list_valid_user teste la validité d'une requête utilisateur et vérifie la réponse.
- test_list_invaliduser teste la récupération d'un utilisateur invalide et vérifie la réponse.
import pytest import requests import json @pytest.mark.parametrize("userid, firstname",[(1,"George"),(2,"Janet")]) def test_list_valid_user(supply_url,userid,firstname): url = supply_url + "/users/" + str(userid) resp = requests.get(url) j = json.loads(resp.text) assert resp.status_code == 200, resp.text assert j['data']['id'] == userid, resp.text assert j['data']['first_name'] == firstname, resp.text def test_list_invaliduser(supply_url): url = supply_url + "/users/50" resp = requests.get(url) assert resp.status_code == 404, resp.text
Ensuite, le fichier test_login_user.py contient les méthodes de test permettant de tester la fonctionnalité de connexion.
- test_login_valid teste la validité de la tentative de connexion avec un e-mail et un mot de passe.
- test_login_no_password teste la tentative de connexion invalide sans saisir de mot de passe.
- test_login_no_email teste la tentative de connexion invalide sans transmettre d'adresse e-mail.
import pytest import requests import json def test_login_valid(supply_url): url = supply_url + "/login/" data = {'email':'test@test.com','password':'something'} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 200, resp.text assert j['token'] == "QpwL5tke4Pnpja7X", resp.text def test_login_no_password(supply_url): url = supply_url + "/login/" data = {'email':'test@test.com'} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 400, resp.text assert j['error'] == "Missing password", resp.text def test_login_no_email(supply_url): url = supply_url + "/login/" data = {} resp = requests.post(url, data=data) j = json.loads(resp.text) assert resp.status_code == 400, resp.text assert j['error'] == "Missing email or username", resp.text
Exécutez le test avec la commande py.test -v et observez le résultat comme suit.
test_list_user.py::test_list_valid_user[1-George] PASSED test_list_user.py::test_list_valid_user[2-Janet] PASSED test_list_user.py::test_list_invaliduser PASSED test_login_user.py::test_login_valid PASSED test_login_user.py::test_login_no_password PASSED test_login_user.py::test_login_no_email PASSED
Mettez à jour les tests et essayez différentes sorties.




