web-dev-qa-db-fra.com

Comment paramétrer un appareil Pytest

Considérez le Pytest suivant:

import pytest

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture
def timeline():
    return TimeLine()

def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

if __name__ == "__main__":
    pytest.main([__file__])

Le test test_timeline utilise un appareil Pytest, timeline, qui a lui-même l'attribut instances. Cet attribut est itéré dans le test, de sorte que le test ne réussit que si l'assertion est vérifiée pour chaque instance dans timeline.instances.

Cependant, ce que j'aimerais réellement faire, c'est générer 3 tests, dont 2 devraient réussir et 1 échouer. J'ai essayé

@pytest.mark.parametrize("instance", timeline.instances)
def test_timeline(timeline):
    assert instance % 2 == 0

mais cela conduit à

AttributeError: 'function' object has no attribute 'instances'

Si je comprends bien, dans les appareils Pytest, la fonction "devient" sa valeur de retour, mais cela ne semble pas encore être arrivé au moment où le test est paramétré. Comment puis-je configurer le test de la manière souhaitée?

27
Kurt Peek

De https://bitbucket.org/pytest-dev/pytest/issues/349/using-fixtures-in-pytestmarkparametrize , il semblerait qu'il ne soit actuellement pas possible d'utiliser des appareils dans pytest.mark.parametrize.

4
Kurt Peek

Ceci est en fait possible via paramétrage indirect .

Cet exemple fait ce que vous voulez avec pytest 3.1.2:

import pytest

class TimeLine:
    def __init__(self, instances):
        self.instances = instances

@pytest.fixture
def timeline(request):
    return TimeLine(request.param)

@pytest.mark.parametrize(
    'timeline',
    ([1, 2, 3], [2, 4, 6], [6, 8, 10]),
    indirect=True
)
def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

if __name__ == "__main__":
    pytest.main([__file__])

Voir aussi cette question similaire .

31
imiric

au lieu de la paramétrisation indirecte, ou de ma solution hacky ci-dessous impliquant l'héritage, vous pouvez également utiliser l'argument params pour @pytest.fixture() - je pense que c'est la solution la plus simple peut-être?

import pytest

class TimeLine:
    def __init__(self, instances=[0, 0, 0]):
        self.instances = instances


@pytest.fixture(params=[
    [1, 2, 3], [2, 4, 5], [6, 8, 10]
])
def timeline(request):
    return TimeLine(request.param)


def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures

12
hwjp

L'utilisation de la paramétrisation indirecte fonctionne, mais je trouve la nécessité d'utiliser request.param comme une variable magique sans nom un peu maladroite.

Voici un modèle que j'ai utilisé. C'est maladroit d'une manière différente, sans doute, mais peut-être le préférerez-vous aussi!

import pytest

class TimeLine:
    def __init__(self, instances):
        self.instances = instances


@pytest.fixture
def instances():
    return [0, 0, 0]


@pytest.fixture
def timeline(instances):
    return TimeLine(instances)


@pytest.mark.parametrize('instances', [
    [1, 2, 3], [2, 4, 5], [6, 8, 10]
])
def test_timeline(timeline):
    for instance in timeline.instances:
        assert instance % 2 == 0

le luminaire timeline dépend d'un autre luminaire appelé instances, dont la valeur par défaut est [0,0,0], mais dans le test réel, nous utilisons parametrize pour injecter une série de valeurs différentes pour instances.

l'avantage que je vois, c'est que tout a un bon nom, et vous n'avez pas besoin de passer ce indirect=True drapeau.

4
hwjp

C'est un sujet déroutant, car les gens ont tendance à penser que les luminaires et les paramètres sont la même chose. Ils ne sont pas et ne sont même pas collectés au cours de la même phase du test Pytest. De par leur conception, les paramètres sont censés être collectés lorsque les tests sont collectés (la liste des nœuds de test est en cours de construction), tandis que les appareils sont destinés à être exécutés lorsque les nœuds de test sont exécutés (la liste des nœuds de test est fixe et ne peut pas être modifiée). Le contenu du luminaire ne peut donc pas être utilisé pour modifier le nombre de nœuds de test - c'est le rôle des paramètres. Voir aussi mon réponse détaillée ici .

C'est la raison pour laquelle votre question n'a pas de solution "en l'état": vous devez placer les instances Timeline dans un paramètre, pas dans un fixture. Comme ça:

import pytest

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture(params=TimeLine().instances)
def timeline(request):
    return request.param

def test_timeline(timeline):
    assert timeline % 2 == 0

réponse de Kurt Peek mentionne un autre sujet qui à mon humble avis ajoute de la confusion ici car il n'est pas directement lié à votre question. Puisqu'il est mentionné, et même accepté comme la solution, laissez-moi élaborer: en effet dans pytest, vous ne pouvez pas utiliser un appareil dans un @pytest.mark.parametrize. Mais même si vous le pouviez, cela ne changerait rien.

Étant donné que cette fonctionnalité est désormais disponible dans pytest-cases en version bêta (je suis l'auteur), vous pouvez vous essayer. La seule façon de faire fonctionner votre exemple même avec cette fonctionnalité est toujours la même: pour supprimer la liste de l'appareil. Vous vous retrouvez donc avec:

import pytest
from pytest_cases import pytest_parametrize_plus, fixture_ref

class TimeLine(object):
    instances = [0, 1, 2]

@pytest.fixture(params=TimeLine().instances)
def timeline(request):
    return request.param

@pytest_parametrize_plus("t", [fixture_ref(timeline)])
def test_timeline(t):
    assert t % 2 == 0

Ce qui est le même que l'exemple précédent avec une couche supplémentaire, probablement inutile. Remarque: voir aussi cette discussion .

0
smarie

Votre parametrize paramset référence la fonction. Peut-être que vous voulez faire référence à Timeline.instances, pas à la fonction/timeline/du luminaire:

@pytest.mark.parametrize("instance", Timeline.instances)
def test_func(instance):
    pass

Je pense que vous cherchez à ajouter une marque xfail à un ensemble de paramètres, peut-être avec une raison?

def make_my_tests():
    return [0, 2, mark.xfail(1, reason='not even')]

@mark.parametrize('timeline', paramset=make_my_tests(), indirect=True)
def test_func(timeline):
    assert timeline % 2 == 0
0
msudder