测试开发技术网站
博客
设计
设计
开发
Python
测试
unittest
运维
Linux基础应用
CI/CD
CI/CD
数据库
数据库
云计算
云计算
云原生
云原生
爬虫
爬虫
数据分析
数据分析
人工智能
人工智能
登录
注册
Pytest----Pytest脚本的加载原理
收藏本文
作者:redrose2100 类别: 日期:2022-09-01 07:53:16 阅读:771 次 消耗积分:0 分
[TOC] ![](https://redrose2100.oss-cn-hangzhou.aliyuncs.com/img/7cd47362-951c-11ee-986a-0242ac110004.png) Pytest测试脚本的加载原理实质上是模块的导入原理,pytest把每个测试脚本都作为一个module进行导入,导入的模式当前支持prepend、append和importlib三种模式,默认情况下是prepend模式 # 一、prepend模式 Pytest默认的就是prepend模式,下面以如下的目录结构详细的解析prepared模式下pytest脚本的加载原理。 ```bash demo01/ |----demo02/ |----demo04/ |----__init__.py |----test_demo01.py |----demo03/ |----__init__.py |----test_demo02.py ``` 加载原理分析: (1)pytest识别到test_demo01.py文件后,从当前位置开始向上递归的找带__init__.py文件的目录,直到找不到不止,比如这里就是demo04,因为demo02中没有__init__.py,因此从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的内容 ```python 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中,与上述分析过程完全一致 ```bash $ 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是不同的 ```bash $ 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模式都存在一个问题,那就是要保持导入模块的唯一性,解释这个问题钱先看一个例子 目录结构如下: ```bash 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的代码均如下: ```python 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的时候出现了如下错误,那么错误原因就是这个导入模块重名了 ```bash 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文件,如下 目录结构 ```bash 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面临的潜在的问题,采用的是一种全新的导入方式,这里首先也来看个例子 目录结构 ```bash demo01/ |----demo02/ |----demo04/ |----test_demo01.py |----demo04/ |----test_demo01.py ``` 如果按照prepend或者append的思路分析,这里肯定是执行不起来的,导入模块的名字肯定是重复的,这里也可以执行以下如下: ```bash $ 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,因此也就不会有这个问题了,执行结果如下: ```bash $ 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自动化脚本的加载原理,至此也就明白当前pytest默认情况下采用的是prepared模式,而在这种模式下,如果文件夹中没有__init__.py文件,一定要保持测试文件命名的独一无二性,所以在实践中,为了减少一些潜在的问题,建议在创建文件夹的时候,直接在所有文件夹下创建__init__.py文件,如此则不需要担心测试脚本文件名重名的问题了。
始终坚持开源开放共享精神,同时感谢您的充电鼓励和支持!
版权所有,转载本站文章请注明出处:redrose2100, http://blog.redrose2100.com/article/359
上一篇:
Pytest----Pytest脚本的运行
下一篇:
Pytest----断言assert的使用方法
搜索
个人成就
出版书籍
《Pytest企业级应用实战》
测试开发技术全栈公众号
测试开发技术全栈公众号
DevOps技术交流微信群
加微信邀请进群
常用网站链接
开源软件洞察
云原生技术栈全景图
Python语言官方文档
Golang官方文档
Docker官方文档
Jenkins中文用户手册
Scrapy官方文档
VUE官方文档
Harbor官方文档
openQA官方文档
云原生开源社区
开源中国
Kubernetes中文文档
Markdown语法官方教程
Kubernetes中文社区
Kubersphere官方文档
BootStrap中文网站
JavaScript中文网
NumPy官方文档
Pandas官方文档
GitLink确实开源网站
数据库排名网站
编程语言排名网站
SEO综合查询网站
数学加减法练习自动生成网站
Kickstart Generator
文章分类
最新文章
最多阅读
特别推荐
×
Close
登录
注册
找回密码
登录邮箱:
登录密码:
图片验证码:
注册邮箱:
注册密码:
邮箱验证码:
发送邮件
注册邮箱:
新的密码:
邮箱验证码:
发送邮件