Pytest----自动化脚本的加载原理

简介: Pytest----自动化脚本的加载原理

Pytest测试脚本的加载原理实质上是模块的导入原理,pytest把每个测试脚本都作为一个module进行导入,导入的模式当前支持prepend、append和importlib三种模式,默认情况下是prependd模式

一、prepend模式

Pytest默认的就是prepend模式

目录结构:

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

加载原理分析:

(1)pytest识别到test_demo01.py文件后,从当前位置开始向上递归的找带__init__.py文件的目录,知道找不到不止,比如这里就是demo04

(2)pytest此时把demo04的上一层目录,即demo02的目录路径插入到sys.path的开头,prepend就是表示从头插入

(3)然后开始计算导入模块的相对路径,比如这里是demo04.test_demo01

(4)将此模块导入,然后加入到sys.modeules中,sys.modules是一个字典,key为相对路径,比如这是demo04.test_demo01,value是其对应的模块对象

(5)pytest继续识别到test_demo02.py文件,同样的原理此时找到demo03就是最顶层的带__init__.py的目录,然后把demo03的上一层目录,即demo01的目录插入到sys.path的头

(6)同理,此时导入模块后将demo03.test_demo02加入到sys.modules中

至此pytest就把测试用例加载完成了

test_demo01.py和test_demo02.py内容均如下,这里为了演示加载原理,增加了打印sys.path和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

执行结果如下,从下面的执行结果可以看出,pytest首先把'G:\src\blog\tests\demo01\demo02' 插入到sys.path的第一个元素,然后把demo04.test_demo01 模块写入到sys.modeules中,紧接着又把'G:\src\blog\tests\demo01'插入到sys.path的第一个元素,然后又把demo03.test_demo02插入到sys.modules中,与上述分析过程完全一致

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

二、append模式

append模式整个流程与prepend模式是完全一样的,唯一的区别就是在将找到的目录插入到sys.path的时候,append是插入到sys.path的末尾,prepend是插入到sys.path的开头

可以通过import-mode=append来指定导入模式为append,执行结果如下,可以看出,这里路径已经插入到sys.path的末尾了,这一点与prepend是不同的

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

三、prepend和append模式存在的问题

prepend和append模式都存在一个问题,那就是要保持导入模块的唯一性,解释这个问题钱先看一个例子

目录结构如下:

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

首先根据上面的导入原理分析一下,这里可以很容易地分析出,不论是prepend模式还是append模式,最终两个test_demo01.py要导入的模块名都是 demo01.test_demo01,在导入这两个模块后,将他们写入sys.modules时肯定是会报错的,因为sys.modules是一个字典类型的,字典类型的key是不允许重复的

两个test_demo01.py的代码均如下:

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

执行结果如下,与上述分析结果是一致的,换言之,如果执行pytest的时候出现了如下错误,那么错误原因就是这个导入模块重名了

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

解决这个问题比较简单的一个方法就是,在每个文件夹中都加一个__init__.py文件,如下

目录结构

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

这样一来继续分析一下,第一个test_demo01.py往上找,发现demo01是最后一个带__init__.py的文件夹,则把demo01的上一层目录加入到sys.path,此时第一个test_demo01.py的导入模块就变为 demo01.demo02.demo04.test_demo01,同理第二个test_demo01.py的导入模块就变为demo01.demo04.test_demo01,这样就解决了这个问题

也正是这个原因,许多文章或者教程中说pytest要求文件夹必须带__init__.py,甚至有的宣称如果不加__init__.py是不会被识别的,这个是不准确的,看到这里应该都清除这里面的本质原因了,因此,为了减少麻烦,可以保持新建文件夹都直接带上__init__.py文件保证不会出这个问题的

四、importlib模式

importlib模式是pytest6.0以后的版本支持的新的方式,importlib方式不再需要修改sys.path和sys.modules,因此不存在上面prepend和append面临的潜在的问题,采用的是一种全新的导入方式,这里首先也来看个例子

目录结构

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

如果按照prepend或者append的思路分析,这里肯定是执行不起来的,导入模块的名字肯定是重复的,这里也可以执行以下如下:

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

但是因为importlib模式不会去修改sys.paht和sys.mo,因此也就不会有这个问题了,执行结果如下:

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

pytest官方文档明确说明了后续将考虑把importlib模式作为默认方式

目录
相关文章
|
4天前
|
存储 Python
Python自动化脚本编写指南
【10月更文挑战第38天】本文旨在为初学者提供一条清晰的路径,通过Python实现日常任务的自动化。我们将从基础语法讲起,逐步引导读者理解如何将代码块组合成有效脚本,并探讨常见错误及调试技巧。文章不仅涉及理论知识,还包括实际案例分析,帮助读者快速入门并提升编程能力。
21 2
|
6天前
|
运维 监控 Python
自动化运维:使用Python脚本简化日常任务
【10月更文挑战第36天】在数字化时代,运维工作的效率和准确性成为企业竞争力的关键。本文将介绍如何通过编写Python脚本来自动化日常的运维任务,不仅提高工作效率,还能降低人为错误的风险。从基础的文件操作到进阶的网络管理,我们将一步步展示Python在自动化运维中的应用,并分享实用的代码示例,帮助读者快速掌握自动化运维的核心技能。
18 3
|
12天前
|
运维 监控 应用服务中间件
自动化运维:如何利用Python脚本提升工作效率
【10月更文挑战第30天】在快节奏的IT行业中,自动化运维已成为提升工作效率和减少人为错误的关键技术。本文将介绍如何使用Python编写简单的自动化脚本,以实现日常运维任务的自动化。通过实际案例,我们将展示如何用Python脚本简化服务器管理、批量配置更新以及监控系统性能等任务。文章不仅提供代码示例,还将深入探讨自动化运维背后的理念,帮助读者理解并应用这一技术来优化他们的工作流程。
|
13天前
|
运维 监控 Linux
自动化运维:如何利用Python脚本优化日常任务##
【10月更文挑战第29天】在现代IT运维中,自动化已成为提升效率、减少人为错误的关键技术。本文将介绍如何通过Python脚本来简化和自动化日常的运维任务,从而让运维人员能够专注于更高层次的工作。从备份管理到系统监控,再到日志分析,我们将一步步展示如何编写实用的Python脚本来处理这些任务。 ##
|
1月前
|
测试技术
自动化测试项目学习笔记(五):Pytest结合allure生成测试报告以及重构项目
本文介绍了如何使用Pytest和Allure生成自动化测试报告。通过安装allure-pytest和配置环境,可以生成包含用例描述、步骤、等级等详细信息的美观报告。文章还提供了代码示例和运行指南,以及重构项目时的注意事项。
164 1
自动化测试项目学习笔记(五):Pytest结合allure生成测试报告以及重构项目
|
17天前
|
运维 Prometheus 监控
自动化运维之路:从脚本到DevOps
【10月更文挑战第25天】在数字化时代的浪潮中,运维不再是简单的服务器管理,而是成为了企业竞争力的核心。本文将带你走进自动化运维的世界,探索如何通过技术手段提升效率和稳定性,以及实现快速响应市场的能力。我们将一起学习如何从基础的脚本编写进化到全面的DevOps实践,包括工具的选择、流程的优化以及文化的建设。无论你是运维新手还是资深专家,这篇文章都将为你提供有价值的见解和实用的技巧。
17 3
|
19天前
|
JSON 测试技术 持续交付
自动化测试与脚本编写:Python实践指南
自动化测试与脚本编写:Python实践指南
24 1
|
1月前
|
人工智能 运维 Devops
自动化运维之路:从脚本到DevOps的转变
【10月更文挑战第7天】在这篇文章中,我们将一起探索自动化运维的演变历程,从最初的简单脚本到现代的DevOps实践。我们将深入理解自动化如何改变了运维工作的本质,并讨论实现这一转变的关键技术和策略。文章将不包含代码示例,而是聚焦于理念、工具和方法论的介绍,旨在为读者提供一个全面的自动化运维框架视图。
|
1月前
|
测试技术 Python
自动化测试项目学习笔记(四):Pytest介绍和使用
本文是关于自动化测试框架Pytest的介绍和使用。Pytest是一个功能丰富的Python测试工具,支持参数化、多种测试类型,并拥有众多第三方插件。文章讲解了Pytest的编写规则、命令行参数、执行测试、参数化处理以及如何使用fixture实现测试用例间的调用。此外,还提供了pytest.ini配置文件示例。
24 2
|
1月前
|
人工智能 运维 监控
自动化运维:从脚本到工具的演变之路
【10月更文挑战第8天】在数字化时代的浪潮中,运维不再是简单的硬件维护,它已经演变成一场关于效率、稳定性和创新的技术革命。本文将带您领略自动化运维的魅力,从最初的脚本编写到现代复杂的自动化工具,我们将一探究竟,看看这些工具如何帮助运维人员简化日常任务,提升工作效率,并最终推动业务发展。