web-dev-qa-db-fra.com

comment partager une variable entre les modules pour tous les tests dans py.test

J'ai plusieurs tests exécutés par py.test qui sont situés dans plusieurs classes dans plusieurs fichiers.

Quelle est la façon la plus simple de partager un grand dictionnaire - que je ne veux pas dupliquer - avec chaque méthode de chaque classe dans chaque fichier à utiliser par py.test?

En bref, je dois créer une "variable globale" pour chaque test. En dehors de py.test, je n'ai aucune utilité pour cette variable, donc je ne veux pas la stocker dans les fichiers testés. J'ai souvent utilisé les luminaires de py.test, mais cela semble exagéré pour ce besoin. C'est peut-être le seul moyen?

22
Trevor

Mise à jour: le hook pytest-namespace est obsolète/supprimé . Ne pas utiliser. Voir # 3735 pour plus de détails.

Vous mentionnez l'option évidente et la moins magique: utiliser un appareil. Vous pouvez l'appliquer à des modules entiers en utilisant pytestmark = pytest.mark.usefixtures('big_dict') dans votre module, mais il ne sera pas dans votre espace de noms, il est donc préférable de le demander explicitement.

Alternativement, vous pouvez affecter des choses dans l'espace de noms pytest à l'aide du crochet:

# conftest.py

def pytest_namespace():
    return {'my_big_dict': {'foo': 'bar'}}

Et maintenant, vous avez pytest.my_big_dict. Le luminaire est probablement encore plus agréable.

17
flub

Il y a des tonnes de choses que j'aime dans py.test, mais une chose que je DÉTESTE absolument, c'est à quel point il joue mal avec les outils d'intelligence de code. Je ne suis pas d'accord pour dire qu'un montage automatique pour déclarer une variable est la méthode "la plus claire" dans ce cas car non seulement cela déroute complètement mon linter, mais aussi toute autre personne qui ne sait pas comment py.test travaux. Il y a beaucoup de magie là-bas, imo.

Donc, une chose que vous pouvez faire qui ne fait pas exploser votre linter et ne nécessite pas de passe-partout TestCase est de créer un module appelé globals. À l'intérieur de ce module, écrivez le nom des éléments que vous souhaitez global à {} ou None et importez le module global dans vos tests. Ensuite, dans votre fichier conftest.py, utilisez les crochets py.test pour définir (ou réinitialiser) vos variables globales selon les besoins. Cela a l'avantage de vous donner le stub avec lequel travailler lors de la construction des tests et les données complètes pour les tests lors de l'exécution.

Par exemple, vous pouvez utiliser le crochet pytest_configure() pour définir votre dict correctement au démarrage de py.test. Ou, si vous vouliez vous assurer que les données étaient impeccables entre chaque test, vous pouvez utiliser automatiquement un appareil pour affecter votre variable globale à votre état connu avant chaque test.

# globals.py
my_data = {}  # Create a stub for your variable


# test_module.py
import globals as gbl

def test_foo():
    assert gbl.my_data['foo'] == 'bar'  # The global is in the namespace when creating tests


# conftest.py
import globals as gbl
my_data = {'foo': 'bar'}  # Create the master copy in conftest

@pytest.fixture(autouse=True)
def populate_globals():
    gbl.my_data = my_data  # Assign the master value to the global before each test

Un autre avantage de cette approche est que vous pouvez utiliser l'indicateur de type dans votre module globals pour vous donner du code sur les objets globaux dans votre test, ce qui n'est probablement pas nécessaire pour un dict, mais je le trouve pratique lorsque j'utilise un objet ( comme un pilote Web). :)

13
user2859458

Avoir un grand dictionnaire des globaux que chaque test utilise est probablement une mauvaise idée. Si possible, je suggère de refactoriser vos tests pour éviter ce genre de chose.

Cela dit, voici comment je le ferais: définir un autouse fixture qui ajoute une référence au dictionnaire dans l'espace de noms global de chaque fonction.

Voici du code. Tout est dans le même fichier, mais vous pouvez déplacer le projecteur vers conftest.py au plus haut niveau de vos tests.

import pytest

my_big_global = {'key': 'value'}

@pytest.fixture(autouse=True)
def myglobal(request):
    request.function.func_globals['foo'] = my_big_global

def test_foo():
    assert foo['key'] == 'value'

def test_bar():
    assert foo['key'] == 'bar'

Voici la sortie de quand j'exécute ce code:

$ py.test test_global.py -vv
======================================= test session starts =======================================
platform darwin -- Python 2.7.5 -- py-1.4.20 -- pytest-2.5.2 -- env/bin/python
collected 2 items

test_global.py:9: test_foo PASSED
test_global.py:12: test_bar FAILED

============================================ FAILURES =============================================
____________________________________________ test_bar _____________________________________________

    def test_bar():
>       assert foo['key'] == 'bar'
E       assert 'value' == 'bar'
E         - value
E         + bar

test_global.py:13: AssertionError
=============================== 1 failed, 1 passed in 0.01 seconds ===============================

Notez que vous ne pouvez pas utiliser un appareil de portée de session car vous n'avez pas accès à chaque objet fonction. Pour cette raison, je m'assure de définir mon grand dictionnaire global une fois et d'utiliser des références - si je définissais le dictionnaire dans cette déclaration d'affectation, une nouvelle copie serait faite à chaque fois.

En conclusion, faire quelque chose comme ça est probablement une mauvaise idée. Bonne chance quand même :)

8
Frank T

Je suis surpris qu'aucune réponse n'ait encore mentionné la mise en cache: depuis la version 2.8, pytest dispose d'un puissant mécanisme de cache.

Exemple d'utilisation

@pytest.fixture(autouse=True)
def init_cache(request):
    data = request.config.cache.get('my_data', None)
    data = {'spam': 'eggs'}
    request.config.cache.set('my_data', data)

Accédez à la dictée des données dans les tests via le dispositif request intégré:

def test_spam(request):
    data = request.config.cache.get('my_data')
    assert data['spam'] == 'eggs'

Partage des données entre les tests

La chose cool à propos de request.cache est qu'il est conservé sur le disque, il peut donc être partagé entre plusieurs tests. Cela est pratique lorsque vous exécutez des tests distribués (pytest-xdist) ou avoir une génération de données longue durée qui ne change pas une fois générée:

@pytest.fixture(autouse=True)
def generate_data(request):
    data = request.config.cache.get('my_data', None)
    if data is None:
        data = long_running_generation_function()
        request.config.cache.set('my_data', data)

Désormais, les tests n'auront plus besoin de recalculer la valeur sur différentes exécutions de test, sauf si vous effacez explicitement le cache sur le disque. Jetez un œil à ce qui se trouve actuellement dans le cache:

$ pytest --cache-show
...
my_data contains:
  {'spam': 'eggs'}

Relancez les tests avec le --cache-clear flag pour supprimer le cache et forcer le recalcul des données. Ou supprimez simplement le .pytest_cache répertoire dans le répertoire racine du projet.

Où aller en partant d'ici

La section connexe dans pytest docs: Cache: travailler avec l'état cross-testrun .

5
hoefling

Vous pouvez ajouter votre variable globale en option dans le pytest_addoption crochet. Il est possible de le faire explicitement avec addoption ou d'utiliser set_defaults méthode si vous voulez que votre attribut soit déterminé sans aucune inspection de la ligne de commande, docs


Lorsque l'option a été définie, vous pouvez la coller dans n'importe quel appareil avec request.config.getoption, puis passez-le au test explicitement ou avec autouse. Alternativement, vous pouvez passer votre option dans presque tous les hooks à l'intérieur de l'objet config.

#conftest.py
def pytest_addoption(parser):    
    parser.addoption("--my_global_var", default="foo")
    parser.set_defaults(my_hidden_var="bar")

@pytest.fixture()
def my_hidden_var(request):
    return request.config.getoption("my_hidden_var")

#test.py
def test_my_hidden_var(my_hidden_var):
    assert my_hidden_var == "bar"
1
Kirill