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模式作为默认方式

目录
相关文章
|
22天前
|
Python
自动化微信朋友圈:Python脚本实现自动发布动态
本文介绍如何使用Python脚本自动化发布微信朋友圈动态,节省手动输入的时间。主要依赖`pyautogui`、`time`、`pyperclip`等库,通过模拟鼠标和键盘操作实现自动发布。代码涵盖打开微信、定位朋友圈、准备输入框、模拟打字等功能。虽然该方法能提高效率,但需注意可能违反微信使用条款,存在风险。定期更新脚本以适应微信界面变化也很重要。
135 61
|
2月前
|
数据采集 监控 数据挖掘
Python自动化脚本:高效办公新助手###
本文将带你走进Python自动化脚本的奇妙世界,探索其在提升办公效率中的强大潜力。随着信息技术的飞速发展,重复性工作逐渐被自动化工具取代。Python作为一门简洁而强大的编程语言,凭借其丰富的库支持和易学易用的特点,成为编写自动化脚本的首选。无论是数据处理、文件管理还是网页爬虫,Python都能游刃有余地完成任务,极大地减轻了人工操作的负担。接下来,让我们一起领略Python自动化脚本的魅力,开启高效办公的新篇章。 ###
|
16天前
|
存储 测试技术 API
pytest接口自动化测试框架搭建
通过上述步骤,我们成功搭建了一个基于 `pytest`的接口自动化测试框架。这个框架具备良好的扩展性和可维护性,能够高效地管理和执行API测试。通过封装HTTP请求逻辑、使用 `conftest.py`定义共享资源和前置条件,并利用 `pytest.ini`进行配置管理,可以大幅提高测试的自动化程度和执行效率。希望本文能为您的测试工作提供实用的指导和帮助。
76 15
|
16天前
|
Web App开发 数据采集 JavaScript
Chrome浏览器实例的TypeScript自动化脚本
Chrome浏览器实例的TypeScript自动化脚本
|
1月前
|
Android开发 开发者 Python
通过标签清理微信好友:Python自动化脚本解析
微信已成为日常生活中的重要社交工具,但随着使用时间增长,好友列表可能变得臃肿。本文介绍了一个基于 Python 的自动化脚本,利用 `uiautomator2` 库,通过模拟用户操作实现根据标签批量清理微信好友的功能。脚本包括环境准备、类定义、方法实现等部分,详细解析了如何通过标签筛选并删除好友,适合需要批量管理微信好友的用户。
68 7
|
1月前
|
运维 Kubernetes Devops
自动化运维:从脚本到工具的演进之旅
在数字化浪潮中,自动化运维成为提升效率、保障系统稳定的关键。本文将探索自动化运维的发展脉络,从基础的Shell脚本编写到复杂的自动化工具应用,揭示这一技术变革如何重塑IT运维领域。我们将通过实际案例,展示自动化运维在简化工作流程、提高响应速度和降低人为错误中的重要作用。无论你是初学者还是资深专家,这篇文章都将为你提供宝贵的洞见和实用的技巧。
|
2月前
|
SQL 测试技术 API
如何编写API接口的自动化测试脚本
本文详细介绍了编写API自动化测试脚本的方法和最佳实践,涵盖确定测试需求、选择测试框架、编写测试脚本(如使用Postman和Python Requests库)、参数化和数据驱动测试、断言和验证、集成CI/CD、生成测试报告及维护更新等内容,旨在帮助开发者构建高效可靠的API测试体系。
|
2月前
|
运维 Devops
自动化运维:从脚本到DevOps的进化之旅
在数字化时代,自动化运维不仅是提高生产效率的关键,更是企业竞争力的象征。本文将带领读者穿越自动化运维的发展历程,从最初的脚本编写到现代DevOps文化的形成,揭示这一演变如何重塑IT行业的工作模式。通过具体案例,我们将展示自动化工具和实践如何简化复杂任务,优化流程,并促进团队协作。你将发现,自动化运维不仅关乎技术的进步,更体现了人、流程和技术三者之间协同增效的深层逻辑。
|
2月前
|
监控 数据挖掘 数据安全/隐私保护
Python脚本:自动化下载视频的日志记录
Python脚本:自动化下载视频的日志记录
|
2月前
|
机器学习/深度学习 人工智能 运维
自动化运维之路:从脚本到工具的演进
在IT运维领域,效率和准确性是衡量工作成效的关键指标。随着技术的发展,自动化运维逐渐成为提升这两个指标的重要手段。本文将带领读者了解自动化运维的演变历程,从最初的简单脚本编写到现今复杂的自动化工具应用,展示如何通过技术提升运维效率。文章不仅介绍理论和实践案例,还提供了代码示例,帮助读者理解自动化运维的实际应用场景。

热门文章

最新文章