Recherche…


Mise en place de py.test

py.test est l'une des bibliothèques de test tierces disponibles pour Python. Il peut être installé en utilisant pip with

pip install pytest

Le code à tester

Disons que nous testons une fonction d’ajout dans projectroot/module/code.py :

# projectroot/module/code.py
def add(a, b):
    return a + b

Le code de test

Nous créons un fichier de test dans projectroot/tests/test_code.py . Le fichier doit commencer par test_ pour être reconnu en tant que fichier de test.

# projectroot/tests/test_code.py
from module import code


def test_add():
    assert code.add(1, 2) == 3

Lancer le test

À partir de projectroot nous projectroot simplement py.test :

# ensure we have the modules
$ touch tests/__init__.py
$ touch module/__init__.py
$ py.test
================================================== test session starts ===================================================
platform darwin -- Python 2.7.10, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /projectroot, inifile:
collected 1 items

tests/test_code.py .

================================================ 1 passed in 0.01 seconds ================================================

Essais défaillants

Un test défaillant fournira des informations utiles sur ce qui a mal tourné:

# projectroot/tests/test_code.py
from module import code


def test_add__failing():
    assert code.add(10, 11) == 33

Résultats:

$ py.test
================================================== test session starts ===================================================
platform darwin -- Python 2.7.10, pytest-2.9.2, py-1.4.31, pluggy-0.3.1
rootdir: /projectroot, inifile:
collected 1 items

tests/test_code.py F

======================================================== FAILURES ========================================================
___________________________________________________ test_add__failing ____________________________________________________

    def test_add__failing():
>       assert code.add(10, 11) == 33
E       assert 21 == 33
E        +  where 21 = <function add at 0x105d4d6e0>(10, 11)
E        +    where <function add at 0x105d4d6e0> = code.add

tests/test_code.py:5: AssertionError
================================================ 1 failed in 0.01 seconds ================================================

Introduction aux tests

Des tests plus compliqués doivent parfois être configurés avant d’exécuter le code que vous souhaitez tester. Il est possible de le faire dans la fonction de test elle-même, mais vous vous retrouvez avec de grosses fonctions de test à tel point qu'il est difficile de déterminer où la configuration s'arrête et que le test commence. Vous pouvez également obtenir beaucoup de code de configuration en double entre vos différentes fonctions de test.

Notre fichier de code:

# projectroot/module/stuff.py
class Stuff(object):
    def prep(self):
        self.foo = 1
        self.bar = 2

Notre fichier de test:

# projectroot/tests/test_stuff.py
import pytest
from module import stuff


def test_foo_updates():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Ce sont des exemples assez simples, mais si notre objet Stuff nécessitait beaucoup plus de configuration, il deviendrait trop compliqué. Nous voyons qu'il y a du code dupliqué entre nos cas de test, alors commençons par le transformer en une fonction distincte.

# projectroot/tests/test_stuff.py
import pytest
from module import stuff


def get_prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff


def test_foo_updates():
    my_stuff = get_prepped_stuff()
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates():
    my_stuff = get_prepped_stuff()
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Cela semble mieux, mais nous avons toujours l' my_stuff = get_prepped_stuff() encombre nos fonctions de test.

luminaires py.test à la rescousse!

Les montages sont des versions beaucoup plus puissantes et flexibles des fonctions de configuration de test. Ils peuvent faire beaucoup plus que ce que nous utilisons ici, mais nous le ferons une étape à la fois.

Nous get_prepped_stuff remplacer get_prepped_stuff par un appareil appelé prepped_stuff . Vous voulez nommer vos appareils avec des noms plutôt que des verbes à cause de la façon dont les appareils seront utilisés dans les fonctions de test eux-mêmes plus tard. @pytest.fixture indique que cette fonction spécifique doit être gérée comme une installation plutôt que comme une fonction normale.

@pytest.fixture
def prepped_stuff():
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    return my_stuff

Maintenant, nous devons mettre à jour les fonctions de test pour qu’elles utilisent le projecteur. Cela se fait en ajoutant un paramètre à leur définition qui correspond exactement au nom du projecteur. Lorsque py.test est exécuté, il lance le fixture avant d'exécuter le test, puis transmet la valeur de retour du fixture dans la fonction de test via ce paramètre. (Notez que les appareils n’ont pas besoin de retourner une valeur; ils peuvent faire d’autres choses, comme appeler une ressource externe, arranger les choses sur le système de fichiers, mettre des valeurs dans une base de données, quels que soient les tests requis)

def test_foo_updates(prepped_stuff):
    my_stuff = prepped_stuff
    assert 1 == my_stuff.foo
    my_stuff.foo = 30000
    assert my_stuff.foo == 30000


def test_bar_updates(prepped_stuff):
    my_stuff = prepped_stuff
    assert 2 == my_stuff.bar
    my_stuff.bar = 42
    assert 42 == my_stuff.bar

Maintenant, vous pouvez voir pourquoi nous l'avons nommé avec un nom. mais la ligne my_stuff = prepped_stuff est quasiment inutile, alors utilisons simplement prepped_stuff place.

def test_foo_updates(prepped_stuff):
    assert 1 == prepped_stuff.foo
    prepped_stuff.foo = 30000
    assert prepped_stuff.foo == 30000


def test_bar_updates(prepped_stuff):
    assert 2 == prepped_stuff.bar
    prepped_stuff.bar = 42
    assert 42 == prepped_stuff.bar

Maintenant, nous utilisons des appareils! Nous pouvons aller plus loin en modifiant la portée de l’appareil (pour qu’il ne s’exécute qu’une seule fois par module d’exécution ou exécution de test suite à la fonction test), en construisant des appareils utilisant d’autres appareils, en paramétrant le les tests utilisant cet appareil sont exécutés plusieurs fois, une fois pour chaque paramètre donné à l'appareil, comme les appareils qui lisent les valeurs du module qui les appelle ... comme mentionné précédemment, les appareils ont beaucoup plus de puissance et de souplesse qu'une fonction normale.

Nettoyage après les tests sont faits.

Disons que notre code a grandi et que notre objet Stuff nécessite maintenant un nettoyage spécial.

# projectroot/module/stuff.py
class Stuff(object):
def prep(self):
    self.foo = 1
    self.bar = 2

def finish(self):
    self.foo = 0
    self.bar = 0

Nous pourrions ajouter du code pour appeler le nettoyage au bas de chaque fonction de test, mais les appareils fournissent un meilleur moyen de le faire. Si vous ajoutez une fonction au fixture et que vous l'enregistrez en tant que finaliseur , le code de la fonction finalizer sera appelé après le test utilisant le fixture. Si la portée de l'appareil est supérieure à une seule fonction (comme un module ou une session), le finaliseur sera exécuté une fois que tous les tests de la portée auront été effectués, donc après l'exécution du module ou à la fin de la session de test .

@pytest.fixture
def prepped_stuff(request):  # we need to pass in the request to use finalizers
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    def fin():  # finalizer function
        # do all the cleanup here
        my_stuff.finish()
    request.addfinalizer(fin)  # register fin() as a finalizer
    # you can do more setup here if you really want to
    return my_stuff

Utiliser la fonction de finaliseur à l’intérieur d’une fonction peut être un peu difficile à comprendre à première vue, surtout lorsque vous avez des appareils plus complexes. Vous pouvez plutôt utiliser un indicateur de rendement pour faire la même chose avec un flux d'exécution plus lisible. La seule différence réelle est qu'au lieu d'utiliser return nous utilisons un yield à la partie du montage où la configuration est effectuée et le contrôle doit aller à une fonction de test, puis ajouter tout le code de nettoyage après le yield . Nous la décorons également en tant que yield_fixture pour que py.test sache comment le gérer.

@pytest.yield_fixture
def prepped_stuff():  # it doesn't need request now!
    # do setup
    my_stuff = stuff.Stuff()
    my_stuff.prep()
    # setup is done, pass control to the test functions
    yield my_stuff
    # do cleanup 
    my_stuff.finish()

Et cela conclut l'Intro à Test Fixtures!

Pour plus d'informations, consultez la documentation officielle sur le luminaire py.test et la documentation officielle sur le rendement.



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow