Cómo compartir una variable a través de módulos para todas las pruebas en py.test

Tengo varias pruebas ejecutadas por py.test que se encuentran en varias clases en varios archivos.

¿Cuál es la forma más sencilla de compartir un diccionario grande, que no quiero duplicar, con cada método de cada clase en cada archivo que use py.test?

En resumen, necesito hacer una “variable global” para cada prueba. Fuera de py.test, no tengo uso para esta variable, por lo que no quiero almacenarla en los archivos que se están probando. Hice uso frecuente de los accesorios de py.test, pero esto parece excesivo para esta necesidad. Tal vez sea la única manera?

Actualización: el enganche pytest-namespace está en desuso / eliminado . No utilice. Vea # 3735 para más detalles.

Mencionas la opción obvia y menos mágica: usar un accesorio. Puede aplicarlo a módulos completos usando pytestmark = pytest.mark.usefixtures('big_dict') en su módulo, pero entonces no estará en su espacio de nombres, por lo que solicitarlo explícitamente podría ser lo mejor.

Alternativamente, puede asignar cosas al espacio de nombres pytest usando el gancho:

 # conftest.py def pytest_namespace(): return {'my_big_dict': {'foo': 'bar'}} 

Y ahora tienes pytest.my_big_dict . El accesorio es probablemente aún mejor.

Hay un montón de cosas que me gustan de py.test, pero una cosa que odio por completo es lo mal que juega con las herramientas de inteligencia de código. No estoy de acuerdo con que un accesorio automático para declarar una variable sea el método “más claro” en este caso porque no solo confunde por completo a mi impresora, sino también a cualquier otra persona que no esté familiarizada con el funcionamiento de py.test. Hay mucha magia allí, imo.

Por lo tanto, una cosa que puede hacer es que no haga que su linter explote y no requiera una repetición de TestCase para crear un módulo llamado global. Dentro de este módulo, apunte los nombres de las cosas que desea global a {} o Ninguno e importe el módulo global a sus pruebas. Luego, en su archivo conftest.py, use los enganches py.test para establecer (o restablecer) sus variables globales según corresponda. Esto tiene la ventaja de brindarle el talón con el que trabajar cuando cree pruebas y los datos completos para las pruebas en tiempo de ejecución.

Por ejemplo, puede usar el pytest_configure() para establecer su dictado justo cuando se inicia py.test. O, si quería asegurarse de que los datos estuvieran prístinos entre cada prueba, podría elegir un dispositivo para asignar su variable global a su estado conocido antes de cada prueba.

 # 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 

Otra ventaja de este enfoque es que puede usar sugerencias de tipo en su módulo global para completar el código en los objetos globales en su prueba, lo que probablemente no sea necesario para un dict, pero me parece útil cuando estoy usando un objeto ( como webdriver). 🙂

Tener un gran diccionario de variables globales que utiliza cada prueba es probablemente una mala idea. Si es posible, sugiero refactorizar sus pruebas para evitar este tipo de cosas.

Dicho esto, así es como lo haría: definir un dispositivo de autoayuda que agregue una referencia al diccionario en el espacio de nombres global de cada función.

Aquí hay algo de código. Todo está en el mismo archivo, pero puede mover el dispositivo a conftest.py en el nivel superior de sus pruebas.

 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' 

Aquí está la salida de cuando ejecuto este código:

 $ 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 =============================== 

Tenga en cuenta que no puede utilizar un dispositivo con ámbito de sesión porque entonces no tiene acceso a cada objeto de función. Debido a esto, me aseguro de definir mi gran diccionario global una vez y usar referencias a él; si definiera el diccionario en esa statement de asignación, se haría una nueva copia cada vez.

Para terminar, hacer algo como esto es probablemente una mala idea. Buena suerte aunque 🙂

Estoy sorprendido de que ninguna respuesta haya mencionado el almacenamiento en caché todavía: desde la versión 2.8, pytest tiene un mecanismo de caché poderoso.

Ejemplo de uso

 @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) 

Acceda al dictado de datos en las pruebas a través del dispositivo de request integrado:

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

Compartir los datos entre ejecuciones de prueba

Lo bueno de request.cache es que se conserva en el disco, por lo que incluso se puede compartir entre ejecuciones de prueba. Esto es útil cuando ejecuta pruebas distribuidas ( pytest-xdist ) o tiene una generación de datos de larga ejecución que no cambia una vez que se genera:

 @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) 

Ahora las pruebas no tendrán que volver a calcular el valor en diferentes ejecuciones de prueba a menos que borre la memoria caché en el disco explícitamente. Eche un vistazo a lo que hay actualmente en el caché:

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

--cache-clear las pruebas con el --cache-clear para eliminar la memoria caché y forzar la recalculo de los datos. O simplemente elimine el directorio .pytest_cache en el directorio raíz del proyecto.

A dónde ir desde aquí

La sección relacionada en documentos de pytest : Caché: trabajando con estado de prueba cruzada .

Puede agregar su variable global como una opción dentro del gancho pytest_addoption . Es posible hacerlo explícitamente con el addoption o usar set_defaults si desea que su atributo se determine sin ninguna inspección de la línea de comandos, docs


Cuando se definió la opción, puede pegarla dentro de cualquier accesorio con request.config.getoption y luego pasarla a la prueba explícitamente o con autouse. Alternativamente, puede pasar su opción a casi cualquier gancho dentro del objeto de 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"