Principio de carga del script Pytest

[Enlace original] Principio de carga del script Pytest


El principio de carga de scripts de prueba de Pytest es esencialmente el principio de importación de módulos. Pytest importa cada script de prueba como un módulo. El modo de importación actualmente admite tres modos: anteponer, agregar e importlib. De forma predeterminada, es el modo anteponer.

1. modo anteponer

El modo predeterminado de pytest es el modo antepuesto. La siguiente estructura de directorio se utiliza para analizar en detalle el principio de carga del script pytest en el modo preparado.

demo01/
  |----demo02/
         |----demo04/
                |----__init__.py
                |----test_demo01.py
  |----demo03/
         |----__init__.py
         |----test_demo02.py

Análisis del principio de carga:

(1) Después de que pytest reconoce el archivo test_demo01.py, busca recursivamente el directorio con el archivo __init__.py desde la ubicación actual hasta que no puede encontrarlo. Por ejemplo, aquí está demo04, porque no hay __init__.py en demo02 , entonces el directorio superior con el archivo __init__.py encontrado en test_demo01.py es demo04

(2) En este momento, pytest inserta el directorio superior de demo04, es decir, la ruta del directorio demo02, al principio de sys.path, y anteponer significa insertar desde el principio.

(3) Luego comience a calcular la ruta relativa del módulo importado, por ejemplo, aquí está demo04.test_demo01

(4) Importe este módulo y luego agréguelo a sys.modules. sys.modules es un diccionario, la clave es una ruta relativa, por ejemplo, esto es demo04.test_demo01 y el valor es su objeto de módulo correspondiente.

(5) Pytest continúa reconociendo el archivo test_demo02.py. El mismo principio encuentra que demo03 es el directorio de nivel superior con __init__.py, y luego inserta el directorio de nivel superior de demo03, es decir, el directorio de demo01, en cabecera sys.path

(6) De manera similar, agregue demo03.test_demo02 a sys.modules después de importar el módulo.

Hasta ahora, pytest ha cargado los casos de prueba.

Los contenidos de test_demo01.py y test_demo02.py son los siguientes: aquí, para demostrar el principio de carga, se agregan los contenidos de impresión sys.path y sys.modules.

import sys

print(f"sys.path:{
      
      sys.path}")
for elem in sys.modules.keys():
    if "demo" in elem:
        print(f"module:{
      
      elem}")

def test_func():
    assert 1==1

Los resultados de la ejecución son los siguientes: Como se puede ver en los siguientes resultados de ejecución, pytest primero inserta 'G:\src\blog\tests\demo01\demo02' en el primer elemento de sys.path y luego escribe demo04.test_demo01. módulo en En sys.modeules, inserte 'G:\src\blog\tests\demo01' en el primer elemento de sys.path y luego inserte demo03.test_demo02 en sys.modules, que es exactamente el mismo que el proceso de análisis anterior. unánime

$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collecting ... sys.path:['G:\\src\\blog\\tests\\demo01\\demo02', 'D:\\python39\\Scripts\\pytest.exe', 'D:\\python39\\python39.zip', 'D:\\python39\\DLLs', 'D:\\python39\\
lib', 'D:\\python39', 'D:\\python39\\lib\\site-packages']
module:demo04
module:demo04.test_demo01
sys.path:['G:\\src\\blog\\tests\\demo01', 'G:\\src\\blog\\tests\\demo01\\demo02', 'D:\\python39\\Scripts\\pytest.exe', 'D:\\python39\\python39.zip', 'D:\\python39\\DLLs'
, 'D:\\python39\\lib', 'D:\\python39', 'D:\\python39\\lib\\site-packages']
module:demo04
module:demo04.test_demo01
module:demo03
module:demo03.test_demo02
collected 2 items                                                                                                                                                       

demo01\demo02\demo04\test_demo01.py .
demo01\demo03\test_demo02.py .

========================================================================== 2 passed in 0.08s ===========================================================================

Dos, modo agregar

Todo el proceso del modo agregar es exactamente el mismo que el del modo anteponer, la única diferencia es que al insertar el directorio encontrado en sys.path, el agregado se inserta al final de sys.path y el antepuesto se inserta al comienzo de ruta.sys.

Puede usar import-mode=append para especificar el modo de importación como anexar. Los resultados de la ejecución son los siguientes. Se puede ver que la ruta aquí se ha insertado al final de sys.path, que es diferente de anteponer

$ pytest -s --import-mode=append
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collecting ... sys.path:['D:\\python39\\Scripts\\pytest.exe', 'D:\\python39\\python39.zip', 'D:\\python39\\DLLs', 'D:\\python39\\lib', 'D:\\python39', 'D:\\python39\\lib
\\site-packages', 'G:\\src\\blog\\tests\\demo01\\demo02']
module:demo04
module:demo04.test_demo01
sys.path:['D:\\python39\\Scripts\\pytest.exe', 'D:\\python39\\python39.zip', 'D:\\python39\\DLLs', 'D:\\python39\\lib', 'D:\\python39', 'D:\\python39\\lib\\site-packages
', 'G:\\src\\blog\\tests\\demo01\\demo02', 'G:\\src\\blog\\tests\\demo01']
module:demo04
module:demo04.test_demo01
module:demo03
module:demo03.test_demo02
collected 2 items                                                                                                                                                       

demo01\demo02\demo04\test_demo01.py .
demo01\demo03\test_demo02.py .

========================================================================== 2 passed in 0.04s ===========================================================================

3. Problemas con los modos anteponer y agregar

Hay un problema tanto en el modo anteponer como en el modo anexar, es decir, mantener la unicidad de los módulos importados. Para explicar este problema, primero veamos un ejemplo.

La estructura del directorio es la siguiente:

demo01/
  |----demo02/
         |----demo04/
                |----__init__.py
                |----test_demo01.py
  |----demo04/
         |----__init__.py
         |----test_demo01.py

Primero, analícelo de acuerdo con el principio de importación anterior. Se puede analizar fácilmente aquí, ya sea en modo anteponer o en modo agregar, los nombres de los módulos finales de los dos test_demo01.py que se importarán son demo01.test_demo01. Después de importar estos dos módulos , Al escribirlos en sys.modules, definitivamente se informará un error, porque sys.modules es un tipo de diccionario y no se permite repetir la clave del tipo de diccionario

Los códigos de los dos test_demo01.py son los siguientes:

import sys

print(f"sys.path:{
      
      sys.path}")
for elem in sys.modules.keys():
    if "demo" in elem:
        print(f"module:{
      
      elem}")

def test_func():
    assert 1==1

Los resultados de la ejecución son los siguientes, lo cual es consistente con los resultados del análisis anterior: en otras palabras, si ocurre el siguiente error al ejecutar pytest, la causa del error es que el módulo importado tiene el mismo nombre.

pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collecting ... sys.path:['G:\\src\\blog\\tests\\demo01\\demo02', 'D:\\python39\\Scripts\\pytest.exe', 'D:\\python39\\python39.zip', 'D:\\python39\\DLLs', 'D:\\python39\\
lib', 'D:\\python39', 'D:\\python39\\lib\\site-packages']
module:demo04
module:demo04.test_demo01
collected 1 item / 1 error                                                                                                                                              

================================================================================ ERRORS ================================================================================
____________________________________________________________ ERROR collecting demo01/demo04/test_demo01.py _____________________________________________________________
import file mismatch:
imported module 'demo04.test_demo01' has this __file__ attribute:
  G:\src\blog\tests\demo01\demo02\demo04\test_demo01.py
which is not the same as the test file we want to collect:
  G:\src\blog\tests\demo01\demo04\test_demo01.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules
======================================================================= short test summary info ========================================================================
ERROR demo01/demo04/test_demo01.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=========================================================================== 1 error in 0.16s ===========================================================================

Una forma relativamente sencilla de resolver este problema es agregar un archivo __init__.py en cada carpeta, de la siguiente manera

Estructura de directorios

demo01/
  |----__init__.py
  |----demo02/
         |__init__.py
         |----demo04/
                |----__init__.py
                |----test_demo01.py
  |----demo04/
         |----__init__.py
         |----test_demo01.py

De esta manera, continúe analizando, el primer test_demo01.py busca y encuentra que demo01 es la última carpeta con __init__.py, luego agrega el directorio superior de demo01 a sys.path, en este momento el primero El módulo de importación de test_demo01 .py se convierte en demo01.demo02.demo04.test_demo01. De manera similar, el módulo de importación del segundo test_demo01.py se convierte en demo01.demo04.test_demo01, lo que resuelve este problema.

También es por esta razón que muchos artículos o tutoriales dicen que pytest requiere que la carpeta tenga __init__.py, y algunos incluso afirman que si no se agrega __init__.py no será reconocido, esto no es exacto, consulte aquí Las razones esenciales deben aclararse. Por lo tanto, para reducir el problema, puede mantener el archivo __init__.py directamente en la nueva carpeta para asegurarse de que este problema no ocurra.

Cuatro, modo importlib

El modo importlib es un nuevo método admitido por versiones posteriores a pytest6.0. El método importlib ya no necesita modificar sys.path y sys.modules, por lo que no hay ningún problema potencial al enfrentar el anteponer y agregar anteriores, y se realiza una nueva importación. forma adoptada, veamos primero un ejemplo

Estructura de directorios

demo01/
  |----demo02/
         |----demo04/
                |----test_demo01.py
  |----demo04/
         |----test_demo01.py

Si lo analizas según la idea de anteponer o anexar, aquí no se debe ejecutar y se debe repetir el nombre del módulo importado, aquí también puedes ejecutar lo siguiente:

$ pytest -s
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collecting ... sys.path:['G:\\src\\blog\\tests\\demo01\\demo02\\demo04', 'D:\\python39\\Scripts\\pytest.exe', 'D:\\python39\\python39.zip', 'D:\\python39\\DLLs', 'D:\\py
thon39\\lib', 'D:\\python39', 'D:\\python39\\lib\\site-packages']
module:test_demo01
collected 1 item / 1 error                                                                                                                                              

================================================================================ ERRORS ================================================================================
____________________________________________________________ ERROR collecting demo01/demo04/test_demo01.py _____________________________________________________________
import file mismatch:
imported module 'test_demo01' has this __file__ attribute:
  G:\src\blog\tests\demo01\demo02\demo04\test_demo01.py
which is not the same as the test file we want to collect:
  G:\src\blog\tests\demo01\demo04\test_demo01.py
HINT: remove __pycache__ / .pyc files and/or use a unique basename for your test file modules
======================================================================= short test summary info ========================================================================
ERROR demo01/demo04/test_demo01.py
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
=========================================================================== 1 error in 0.16s ===========================================================================

Pero debido a que el modo importlib no modificará sys.paht y sys.mo, no habrá tal problema. Los resultados de la ejecución son los siguientes:

$ pytest -s --import-mode=importlib
========================================================================= test session starts ==========================================================================
platform win32 -- Python 3.9.6, pytest-6.2.5, py-1.10.0, pluggy-1.0.0
rootdir: G:\src\blog\tests
plugins: allure-pytest-2.9.43, caterpillar-pytest-0.0.2, hypothesis-6.31.6, forked-1.3.0, rerunfailures-10.1, xdist-2.3.0
collecting ... sys.path:['D:\\python39\\Scripts\\pytest.exe', 'D:\\python39\\python39.zip', 'D:\\python39\\DLLs', 'D:\\python39\\lib', 'D:\\python39', 'D:\\python39\\lib
\\site-packages']
sys.path:['D:\\python39\\Scripts\\pytest.exe', 'D:\\python39\\python39.zip', 'D:\\python39\\DLLs', 'D:\\python39\\lib', 'D:\\python39', 'D:\\python39\\lib\\site-packages
']
collected 2 items                                                                                                                                                       

demo01\demo02\demo04\test_demo01.py .
demo01\demo04\test_demo01.py .

========================================================================== 2 passed in 0.06s ===========================================================================

Este es el principio de carga del script de automatización de pytest. Hasta ahora, se entiende que el pytest actual adopta el modo preparado de forma predeterminada. En este modo, si no hay un archivo __init__.py en la carpeta, el archivo de prueba debe tener un nombre Por lo tanto, en la práctica, para reducir algunos problemas potenciales, se recomienda crear archivos __init__.py directamente en todas las carpetas al crear carpetas, para que no tenga que preocuparse por el problema de los nombres de archivos de script de prueba duplicados. .

Supongo que te gusta

Origin blog.csdn.net/redrose2100/article/details/122166965
Recomendado
Clasificación