Tutoriel PyTest : Qu'est-ce que c'est, comment installer, framework, assertions
Qu’est-ce que PyTest ?
PyTest est un framework de test qui permet aux utilisateurs d'écrire des codes de test à l'aide du langage de programmation Python. Il vous aide à rédiger des cas de test simples et évolutifs pour les bases de données, les API ou l'interface utilisateur. PyTest est principalement utilisé pour écrire des tests pour les API. Il permet d'écrire des tests allant des tests unitaires simples aux tests fonctionnels complexes.
Pourquoi utiliser PyTest ?
Certains des avantages de pytest sont
- Très facile à démarrer grâce à sa syntaxe simple et facile.
- Peut exécuter des tests en parallèle.
- Peut exécuter un test spécifique ou un sous-ensemble de tests
- Détecter automatiquement les tests
- Passer les tests
- Source ouverte
Comment installer PyTest
Voici un processus pour installer PyTest :
Étape 1) Vous pouvez installer pytest en
pip install pytest==2.9.1
Une fois l'installation terminée, vous pouvez la confirmer avec par
py.test -h
Cela affichera l'aide
Premier PyTest de base
Nous allons maintenant apprendre à utiliser Pytest avec un exemple de base de PyTest.
Créez un dossier study_pytest. Nous allons créer nos fichiers de test dans ce dossier.
Veuillez accéder à ce dossier dans votre ligne de commande.
Créez un fichier nommé test_sample1.py dans le dossier
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
py.test
Vous obtiendrez une sortie sous la forme
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
Ici dans test_sample1.py F.
F dit échec
Dot(.) indique le succès.
Dans la section des échecs, vous pouvez voir la ou les méthodes ayant échoué et la ligne d'échec. Ici, x==y signifie 5==6, ce qui est faux.
Ensuite, dans ce didacticiel PyTest, nous découvrirons l'assertion dans PyTest.
Assertions dans PyTest
Les assertions Pytest sont des vérifications qui renvoient le statut True ou False. Dans Python Pytest, si une assertion échoue dans une méthode de test, l'exécution de cette méthode y est arrêtée. Le code restant dans cette méthode de test n'est pas exécuté et les assertions Pytest continueront avec 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
assert x == y,"test failed because x=" + str(x) + " y=" + str(y)
Placez ce code dans test_file1_method1() au lieu de l'assertion
assert x == y,"test failed"
L'exécution du test donnera l'échec suivant : AssertionError : test failed x=5 y=6
Comment PyTest identifie les fichiers de test et les méthodes de test
Par défaut, pytest identifie uniquement les noms de fichiers commençant par test_ ou se terminant par _test comme fichiers de test. Nous pouvons cependant mentionner explicitement d'autres noms de fichiers (expliqués plus tard). Pytest exige que les noms des méthodes de test commencent par « test ». Tous les autres noms de méthodes seront ignorés même si nous demandons explicitement d'exécuter ces méthodes.
Voir 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 demander explicitement à pytest de choisir testlogin.py et logintest.py
Voir 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, dans le dossier study_pytest, nous avons un fichier test_sample1.py. Supposons que nous ayons plusieurs fichiers, disons test_sample2.py, test_sample3.py. Pour exécuter tous les tests de tous les fichiers du dossier et des sous-dossiers, nous devons simplement exécuter la commande pytest.
py.test
Cela exécutera tous les noms de fichiers commençant par test_ et les noms de fichiers se terminant par _test dans ce dossier et les sous-dossiers de ce dossier.
Pour exécuter des tests uniquement à partir d'un fichier spécifique, nous pouvons utiliser py.test
py.test test_sample1.py
Exécutez un sous-ensemble de test complet avec PyTest
Parfois, nous ne souhaitons pas exécuter l’intégralité de la suite de tests. Pytest nous permet d'exécuter des tests spécifiques. Nous pouvons le faire de 2 manières
- Regroupement des noms de tests par correspondance de sous-chaîne
- Regroupement des tests par marqueurs
Nous avons déjà 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
• test_sample1.py
• test_file1_method1()
• test_file1_method2()
• test_sample2.py
• test_file2_method1()
• test_file2_method2()
Option 1) Exécuter des tests par correspondance de sous-chaînes
Ici, pour exécuter tous les tests ayant la méthode 1 dans son nom, nous devons exécuter
py.test -k method1 -v
-k <expression> is used to represent the substring to match
-v increases the verbosity
Donc exécuter 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 de courir avec différentes combinaisons telles que : -
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 nous permet de définir divers attributs pour les méthodes de test à l'aide des marqueurs pytest, @pytest.mark . Pour utiliser des marqueurs dans le fichier de test, nous devons importer pytest sur les fichiers de test.
Ici, nous appliquerons différents noms de marqueurs aux méthodes de test et exécuterons des tests spécifiques basés sur les noms de marqueurs. Nous pouvons définir les marqueurs sur chaque nom de test en utilisant
@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 des 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 noté en
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, test_file2_method2.
L'exécution de py.test -m set2 exécutera test_file1_method2.
Exécuter des tests en parallèle avec Pytest
Habituellement, une suite de tests comprendra plusieurs fichiers de test et des centaines de méthodes de test dont l'exécution prendra un temps considérable. Pytest nous permet d'exécuter des tests en parallèle.
Pour cela, nous devons d'abord installer pytest-xdist en exécutant
pip install pytest-xdist
Vous pouvez exécuter des tests maintenant en
py.test -n 4
-n
Agencements Pytest
Les luminaires sont utilisés lorsque nous voulons exécuter du code avant chaque méthode de test. Ainsi, au lieu de répéter le même code à chaque test, nous définissons les appareils. Habituellement, les appareils sont utilisés pour initialiser les connexions à la base de données, transmettre la base, etc.
Une méthode est marquée comme un appareil Pytest en la marquant avec
@pytest.fixture
Une méthode de test peut utiliser un appareil Pytest en mentionnant l'appareil 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 appareil nommé supply_AA_BB_CC. Cette méthode renverra une liste de 3 valeurs.
- Nous disposons de 3 méthodes de test comparant chacune des valeurs.
Chacune des fonctions de test a un argument d'entrée dont le nom correspond à un appareil disponible. Pytest invoque ensuite la méthode luminaire correspondante et les valeurs renvoyées seront stockées dans l'argument d'entrée, ici la liste [25,35,45]. Désormais, les éléments de la liste sont utilisés dans des méthodes de test pour la comparaison.
Maintenant, lancez le test et voyez 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 est réussi puisque zz=BB=35, et les 2 tests restants ont échoué.
La méthode luminaire a une portée uniquement dans le fichier de test dans lequel elle est définie. Si nous essayons d'accéder au luminaire dans un autre fichier de test, nous obtiendrons une erreur indiquant que le luminaire 'supply_AA_BB_CC' est introuvable pour les méthodes de test dans d'autres fichiers.
Pour utiliser le même appareil sur plusieurs fichiers de test, nous allons créer des méthodes d'appareil dans un fichier appelé conftest.py.
Voyons cela à l'aide de l'exemple PyTest ci-dessous. Créez 3 fichiers conftest.py, test_basic_fixture.py, 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 luminaire dans le fichier de test et s'il ne le trouve pas, il le cherchera dans le conftest.py
Exécutez le test par py.test -k test_comparewith -v pour obtenir le résultat comme 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
Le but du paramétrage d'un test est d'exécuter un test sur plusieurs ensembles d'arguments. Nous pouvons le faire par @pytest.mark.parametrize.
Nous verrons cela avec l'exemple PyTest ci-dessous. Ici, nous allons passer 3 arguments à une méthode de test. Cette méthode de test ajoutera les 2 premiers arguments et les comparera au 3ème argument.
Créez le fichier de test test_addition.py avec le code ci-dessous
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 3 arguments : input1, input2, output. Il ajoute input1 et input2 et compare avec la sortie.
Lançons le test par py.test -k test_add -v et voyons 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 les tests exécutés 2 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
Pytest Xfail/Sauter les tests
Il y aura des situations dans lesquelles nous ne souhaitons pas exécuter de test, ou où un scénario de test n'est pas pertinent à un moment donné. Dans ces situations, nous avons la possibilité de faire échouer le test ou de sauter les tests.
Le test xfailed sera exécuté, mais il ne sera pas compté comme une partie des tests ayant échoué ou réussi. Aucun traçage ne sera affiché si ce test échoue. Nous pouvons échouer aux tests en utilisant
@pytest.mark.xfail.
Sauter un test signifie que le test ne sera pas exécuté. Nous pouvons sauter des tests en utilisant
@pytest.mark.skip.
Modifiez le 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.
- test_add_3 et test_add_4 sont xfailed. Ces tests seront exécutés et feront partie des tests xfailed (sur échec du test) ou xpassed (sur réussite du test). Il n’y aura aucune trace des échecs.
- test_add_5 et test_add_6 seront exécutés et test_add_6 signalera un échec avec traçage pendant que test_add_5 réussit
Exécutez le test par py.test test_addition.py -v et voyez 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 XML
Nous pouvons créer des résultats de tests au format XML que nous pouvons transmettre aux serveurs d'intégration continue pour un traitement ultérieur, etc. Cela peut être fait par
py.test test_sample1.py -v –junitxml=”result.xml”
Le result.xml enregistrera le résultat de l’exécution du test. Trouvez un exemple de 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>
À partir de
Pytest Framework 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 de https://reqres.in/. Ce site Web est uniquement destiné à fournir une API testable. Ce site Web ne stocke pas nos données.
Ici, nous allons écrire quelques tests pour
- listant certains utilisateurs
- se connecter avec les utilisateurs
Créez les fichiers ci-dessous avec le code donné
conftest.py – dispose d'un appareil qui fournira l'URL de base pour toutes les méthodes de test
import pytest
@pytest.fixture
def supply_url():
return "https://reqres.in/api"
test_list_user.py – contient les méthodes de test pour répertorier les utilisateurs valides et invalides
- test_list_valid_user teste la récupération d'un utilisateur valide et vérifie la réponse
- test_list_invaliduser teste la récupération d'utilisateur non valide 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
test_login_user.py – contient des méthodes de test pour tester la fonctionnalité de connexion.
- test_login_valid teste la tentative de connexion valide avec e-mail et mot de passe
- test_login_no_password teste la tentative de connexion invalide sans transmettre le mot de passe
- test_login_no_email teste la tentative de connexion invalide sans transmettre l'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 en utilisant py.test -v
Voir le résultat comme
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
Résumé
Dans ce didacticiel PyTest, nous avons couvert
- Installez pytest en utilisant pip install pytest=2.9.1
- Programme pytest simple et exécutez-le avec la commande py.test.
- Les instructions d'assertion, assert x==y, renverront True ou False.
- Comment pytest identifie les fichiers et les méthodes de test.
- Fichiers de test commençant par test_ ou se terminant par _test
- Méthodes de test commençant par test
- La commande py.test exécutera tous les fichiers de test de ce dossier et de ces sous-dossiers. Pour exécuter un fichier spécifique, nous pouvons utiliser la commande py.test
- Exécuter un sous-ensemble de méthodes de test
- Le regroupement des noms de tests par sous-chaîne matching.py.test -k
-v exécutera tous les tests ayant dans son nom. - Exécutez le test par marqueurs. Marquez les tests en utilisant @pytest.mark.
et exécutez les tests en utilisant pytest -m pour exécuter les tests marqués comme . - Exécuter des tests en parallèle
- Installez pytest-xdist en utilisant pip install pytest-xdist
- Exécutez des tests en utilisant py.test -n NUM où NUM est le nombre de travailleurs
- Création de méthodes de luminaire pour exécuter du code avant chaque test en marquant la méthode avec @pytest.fixture
- La portée d'une méthode de luminaire se trouve dans le fichier dans lequel elle est définie.
- Une méthode de luminaire est accessible sur plusieurs fichiers de test en la définissant dans le fichier conftest.py.
- Une méthode de test peut accéder à un appareil Pytest en l'utilisant comme argument d'entrée.
- Paramétrage des tests pour les exécuter sur plusieurs ensembles d'entrées.
@pytest.mark.parametrize("input1, input2, output",[(5,5,10),(3,5,12)])
def test_add (entrée1, entrée2, sortie) :
affirmer input1+input2 == sortie, "échec"
exécutera le test avec les entrées (5,5,10) et (3,5,12) - Tests Skip/xfail utilisant @pytets.mark.skip et @pytest.mark.xfail
- Créez des résultats de test au format XML qui couvrent les détails des tests exécutés à l'aide de py.test test_sample1.py -v –junitxml="result.xml"
- Un exemple de framework pytest pour tester une API