Tech Blog.

Thoughts, stories, ideas.

Tester avec Pytest

7. May 2017

Si vous voulez tester votre code en Python, vous devriez jeter un coup d’oeil à Pytest comme alternative à Unittest.

Pytest est un framework de test pour Python qui rend le test très facile et sans trop d’effort. Ses avantages sont la simplicité, la découverte automatique des tests, les montages modulaires et la sortie d’erreur intelligente.

Ce billet de blog est destiné à donner un aperçu et une introduction facile au test avec Pytest. Il donne d’abord un aperçu de la façon d’utiliser Pytest, puis discute de la façon de fournir des ressources pour les tests, et conclut avec quelques conseils et astuces.

Introduction

Écrire des tests

Les tests peuvent facilement être écrits avec Pytest. Vous n’avez besoin que de Python’s own Assert-Statement. Pour que Pytest puisse trouver le test, la fonction de test est préfixée par `test_’ :

def test_blog():

assert 1 > 0

Il n’est pas nécessaire d’en faire plus pour tester !

Commencer les tests

Après l’installation via pip (pip install pytest) ou via le gestionnaire de paquets de votre choix, Pytest peut être lancé de deux façons :

  • avec le nom de fichier en argument : pytest foo.py.
  • without argument. Dans ce cas, Pytest recherche et teste tous les fichiers au format *\*\*\_test.py* ou *test\_\*.py*.

Sortie

Si le test est réussi, vous obtenez une vue d’ensemble du résultat du test :

======= test session starts =======

platform linux -- Python 3.5.2, pytest-3.0.5, py-1.4.31, pluggy-0.4.0

rootdir: /home/sh/pytest_blog, inifile:

plugins: factoryboy-1.1.6

collected 1 items

first.py .

======= 1 passed in 0.00 seconds =======

Pytest, cependant, ne montre vraiment sa force qu’avec des tests qui ne fonctionnent pas en faisant une sortie détaillée qui exactement n’a pas réussi. Très agréable en utilisant l’exemple de deux listes qui ne contiennent pas les mêmes éléments :

def test_list():

assert [1, 2] == [1, 2, 3]

résulte en

def test_list():

> assert [1, 2] == [1, 2, 3]

E assert [1, 2] == [1, 2, 3]

E Right contains more items, first extra item: 3

E Use -v to get the full diff

list.py:4: AssertionError

=============== 1 failed in 0.01 seconds ================

Comme vous pouvez le voir, Pytest retourne immédiatement l’élément qui a causé l’erreur !

Options importantes

Quelques options importantes de Pytest qui aident de manière significative dans l’utilisation de Pytest sont les suivantes :

  • -s Empêche la capture des entrées/sorties. Très important, parce que vous ne pouvez pas voir la sortie de vos instructions d’impression en mode normal ou le débogueur ne peut pas être lancé autrement.
  • -k <string> Utilisé pour filtrer les tests. Lance uniquement les tests qui contiennent `string’ comme sous-chaîne.
  • -x Annule le premier test échoué

Le fichier pytest.ini peut être utilisé pour configurer les valeurs par défaut de Pytest.

Montages

Souvent, les tests exigent que certaines ressources soient disponibles pour les tests. Il s’agit par exemple des connexions aux bases de données, des fichiers de configuration, du navigateur pour les tests d’interface utilisateur, et ainsi de suite. Pytest propose le concept de Fixtures. Vous pouvez imaginer des montages un peu comme les fonctions setUp()– et tearDown() des frameworks de test unitaire connus. Cependant, les montages d’octets peuvent être chargés de manière beaucoup plus dynamique par test : pour utiliser un montage dans un test, il ne peut être spécifié qu’en argument dans la fonction :

def test_something_fancy(navigateur) :

browser.visit(foo) # le navigateur est maintenant un appareil chargé pour ce test

Pour créer un projecteur, utilisez le décorateur @pytest.fixture. La valeur requise est retournée avec “yield”. Ainsi, il est possible que le luminaire puisse être retiré à nouveau après la déclaration de rendement. Un exemple de clarification:

1 import os

2 import sqlite3

3

4 import pytest

5

6 @pytest.fixture(scope='function')

7 def db(tmpdir):

8 file = os.path.join(tmpdir.strpath, "test.db")

9

10 conn = sqlite3.connect(file)

11 conn.execute("CREATE TABLE blog (id, title, text)")

12

13 yield conn

14

15 conn.close()

16

17 def test_entry_creation(db):

18 query = ("INSERT INTO blog "

19 "(id, title, text)"

20 "VALUES (?, ?, ?)")

21 values = (1,

22 "PyTest",

23 "Dies ist ein Blogeintrag")

24

25 db.execute(query, values)

Le projecteur fournit une connexion DB pour le test et crée également une table qui est nécessaire pour le test.

Les lignes 6 et 7 sont intéressantes : Sur la ligne 6, le paramètre scope détermine la fréquence d’exécution d’un montage. Vous pouvez utiliser function, class, module et session. Par exemple, si vous sélectionnez l’oscilloscope ” modules “, le projecteur n’est créé qu’une seule fois pour le module entier, puis réutilisé pour chaque test. Ceci est très utile pour les ressources qui n’ont pas nécessairement besoin d’être recréées à chaque fois qu’un test est effectué et peuvent donc être réutilisées.

Sur la ligne 7, un autre luminaire est chargé dans notre luminaire, ce qui est facilement possible. Dans le cas de cet exemple, il s’agit d’un projecteur fourni par Pytest[http://docs.pytest.org/en/latest/builtin.html#builtin-fixtures-function-arguments]. Elle retourne un répertoire temporaire unique à chaque appel test.

Autres caractéristiques importantes

Paramétrer les tests

Avec le Décorateur pytest.mark.parametrize vous pouvez générer des tests avec différents paramètres. Par exemple, nous étendons le test `test_entry_creation’ depuis le haut :

@pytest.mark.parametrize("id,title,text", [

(1, "House Stark", "Winter is coming"),

(2, "House Lannister", "Hear me Roar"),

(3, "House Martell", "Unbowed, Unbent, Unbroken")

])

def test_parametrized_entry_creation(id, title, text, db):

query = ("INSERT INTO blog "

"(id, title, text)"

"VALUES (?, ?, ?)")

values = (id, title, text)

db.execute(query, values)

Trois tests seront alors générés, chacun avec un ensemble de paramètres différents.

Sauter, échouer et marquer

Les tests peuvent être notés de différentes manières :

  • @pytest.mark.skip(reason="Not implemented yet") fait sauter le test par Pytest
  • @pytest.mark.skipif(not os.environ.get('CI', False)) fait sauter le test par Pytest si la condition est remplie
  • @pytest.mark.xfail(reason="Can't work yet")` s’attend à ce qu’un test échoue
  • @pytest.mark.abc autre marqueur. Les tests peuvent être filtrés lorsqu’ils sont appelés. Exemple : lancer tous les tests marqués abc : pytest -m abc. Pour lancer tous les tests *non* marqués avec abc, l’opérateur non peut être utilisé : pytest -m 'not abc'.

Exceptions erwarten

Lorsque des exceptions à ce code ne sont pas disponibles, nous ne pouvons plus utiliser Pytest avec un ContextManager gemacht :

def test_exception() :

>avec pytest.raises(Exception) :

>augmentation (Exception)

Autouse of Fixtures

Enfin, pour qu’un projecteur soit automatiquement disponible dans chaque test, il peut être passé en paramètre dans le décorateur. Le code suivant pourrait être un exemple de la façon dont une connexion se produit à chaque test dans le navigateur:

@pytest.fixture(autouse=True)

def session(login_page, browser):

login_page.login()

yield

browser.delete_all_cookies()

Last but not least….

Pour Pytest, quelque chose plus de 260 plugins existe au moment d’écrire ces lignes. Ils étendent Pytest avec les dernières nouveautés, des extensions amusantes comme le plugin emoji, qui rend la sortie de test un peu plus amusante, aux contrôles étendus comme Import order (Isort) et Syntax Linting with Flake8, aux extensions comme l’intégration du Selenium ou Splinter. Donc si vous manquez une fonctionnalité dans Pytest, vous devriez certainement jeter un coup d’oeil à la liste des plugins – ça vaut le coup !

Dans l’ensemble, Pytest rend le test si facile qu’il n’y a pas d’excuse pour négliger les tests !