Burlándose de un módulo de importación en pytest

Estoy escribiendo un complemento de pytest que debería probar el software que está diseñado para funcionar dentro de un conjunto de entornos específicos.

El software que estoy escribiendo se ejecuta dentro de un marco más grande, lo que hace que ciertos módulos de Python estén disponibles solo cuando se ejecuta mi software de Python dentro del marco.

Para probar mi software, debo “simular” o falsificar un módulo completo (en realidad, bastantes). Necesitaré implementar su funcionalidad de una manera similar, pero mi pregunta es ¿cómo debo hacer que este módulo falso de Python esté disponible para el código de mi software, usando un complemento py.test?

Por ejemplo, supongamos que tengo el siguiente código en uno de mis archivos de origen:

import fwlib def fw_sum(a, b): return fwlib.sum(a, b) 

Sin embargo, el módulo fwlib solo está disponible a través del marco del cual ejecuto mi software, y no puedo realizar pruebas dentro de él.

¿Cómo me aseguraría, dentro de un complemento de pytest, de que un módulo llamado fwlib ya está definido en sys.modules ? Por supuesto, necesitaré implementar fwlib.sum yo mismo. Estoy buscando recomendaciones sobre cómo hacer precisamente eso.

pytest proporciona un accesorio para este caso de uso: monkeypatch.syspath_prepend .

Puede anteponer una ruta a la lista sys.path de las ubicaciones de importación. Escriba un fwlib.py falso e fwlib.py en sus pruebas, agregando el directorio según sea necesario. Al igual que los otros módulos de prueba, no es necesario que se incluya con la distribución.

Después de jugar con esto, no pude averiguar cómo hacer que el dispositivo simule las importaciones de nivel de módulo correctamente desde el código de la biblioteca (si alguien que lee esto encuentra la estructura de directorio correcta para que funcione, ¡publique una respuesta!) .

Sin embargo, puedo ofrecer una solución diferente que funcione: puede inyectar el nombre desde conftest.py , que se importa primero . La siguiente statement de importación solo reutilizará el objeto ya presente en sys.modules .

Estructura del paquete:

 $ tree . . ├── conftest.py ├── lib │  └── my_lib.py └── tests └── test_my_lib.py 2 directories, 3 files 

Contenido de los archivos:

 # conftest.py import sys def fwlib_sum(a, b): return a + b module = type(sys)('fwlib') module.sum = fwlib_sum sys.modules['fwlib'] = module 

archivo de biblioteca:

 # lib/my_lib.py import fwlib def fw_sum(a, b): return fwlib.sum(a, b) 

archivo de prueba:

 # lib/test_my_lib.py import my_lib def test_sum(): assert my_lib.fw_sum(1, 2) == 3