Pytest框架
介绍
1.简介
pytest是纯python编写的自动化测试框架,可以支持python语法编写测试用例,是一个非常成熟的全功能的Python测试框架。
主要有以下几个特点:
简单灵活,容易上手;支持参数化;能够支持简单的单元测试和复杂的功能测试,还可以结合selenium、requests做自动化测试;pytest具有很多第三方插件,并且可以自定义扩展。安装:pip install pytest
官方文档:https://docs.pytest.org
2.第一个pytest用例
def test_01(): assert 1 == 1
3.pytest命名规范
测试模块:以 test_
开头命名,如:test_login.py
,或以 _test
结尾;
测试类:必须以Test
开头命名,且测试类中不能有 __init__
方法;
测试方法/测试函数:必须以test
开头。
4.常用参数
可以通过pytest -h来查看所有可用参数。pytest的参数有很多,下面是归纳一些常用的参数:
无参数:读取路径下符合条件的所有类、函数、方法全部执行;
-v:打印详细运行日志;
-s:输出调试信息,包括print打印的信息;命令行输入:pytest -s;
-x:运行用例失败立即停止运行
–maxfail
用例失败数达到某个设定的值停止运行
pytest --maxfail=[num]
-m 运行所有@pytest.mark.[标记名] 标记的用例
比如:用例标记 @pytest.mark.high
pytest -m=hign 或者 pytest -m hign。# 表示只执行有此标记hight的case。
pytest -m="hign or smoke" 或者 pytest -m "hign or smoke" # 表示两种标记都执行
–reruns=num:失败用例重跑num次。需要安装 pytest-rerunfailures 插件模块。
-n参数,启用多线程或分布式运行测试用例。需要安装pip install pytest-xdist 插件模块。
命令行输入:pytest -vs -n=2
-k: 指定运行某个或某些用例
pytest -k ‘类名’
pytest -k ‘方法名’
pytest -k ‘类名 and not 方法名’ # 运行类里所有方法,不包含某个方法
命令行输入:pytest -vs -k=01
python程序运行pytest:
caseNameString = " or ".join(caseNameList)cmd = f"python -m pytest -k \"{caseNameString}\" --alluredir ./allure --clean-alluredir"os.system(cmd) -k的值支持中文的哟~
参数-n,启用多线程或分布式运行测试用例。需要安装pip install pytest-xdist 插件模块。
命令行输入:pytest -vs -n=2
5.实现数据驱动
Pytest 测试框架的数据驱动是由 pytest 自带的pytest.mark.parametrize()来实现的。
@pytest.mark.parametrize() 装饰器接收两个参数:
第一个参数以字符串的形式存在,它代表能被测试函数所能接受的参数,如果被测试函数有多个参数,则以逗号分隔;
第二个参数用于保存测试数据。如果只有一组数据,以列表的形式存在,如果有多组数据,以列表嵌套元组的形式存在(例如: [1,1]或者[(1,1), (2,2)])。
实例
class Test01: @pytest.mark.parametrize('a,b,expect',[(1,1,1),(2,3,5)]) def test_001(self,a,b,expect): print('测试a+b的结果') assert a+b==expect @pytest.mark.parametrize('c,d,expect', [(2, 4, 2), (9, 10, 1)]) def test_002(self,c,d,expect): assert d-c==expectif __name__ == '__main__': pytest.main([__file__, '-k','test_001'])
6.pytest fixtures
6.1 fixture用途
fixture主要用来做初始化环境以及测试结束后的数据清除。pytest fixture与setup,teardown功能一样,但比之更加灵活,完全可以代替setup,teardown。6.2 fixture参数详解
@pytest.fixture(scope='function',params=None,autouse=False,ids=None,name=None)
yield
fixture装饰器,相当于setup,测试用例的前置
* scope: 有四个级别参数'function(默认)'、'class'、'module'、'session'。
* params:一个可选的参数列表,列表中每个数据都可以作为用例的输入。也就说有多少数据,就会形成多少用例。可以通过request.param来获取该次调用的参数。
* autouse:如果True,自动调用fixture功能。如果为False则需要调用fixture。
* ids:每个字符串id的列表,每个字符串对应于params这样他们就是测试ID的一部分。如果没有提供ID它们将从params自动生成。
* name:fixture的名称。这默认为装饰函数的名称。如果fixture在定义它的统一模块。
yield:这个关键字之后的代码相当于teardown,测试用例的后置。
6.3 fixture的作用范围
fixture里面有个scope参数可以控制fixture的作用范围:session>module>class>function
-function:每一个函数或方法都会调用
-class:每一个类调用一次,一个类中可以有多个方法
-module:每一个.py文件调用一次,该文件内又有多个function和class
-session:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
6.4 调用fixture的三种方法
方式1:函数或类里直接传fixture的函数名称
@pytest.fixture() def test_fixture(self): print('\n用例开始执行fix1\n') yield print('\n用例执行结束fix1\n') def test_a(self, test_fixture): print('runing') assert 1 == 1
执行结果:
方式2: 使用装饰器@pytest.mark.usefixtures()修饰需要运行的用例(可以叠加使用多个装饰器)
@pytest.fixture() def test_fixture(self): print('\n用例开始执行fix1\n') yield print('\n用例执行结束fix1\n') @pytest.fixture() def test_fixture2(self): print('\n用例开始执行fix2\n') yield print('\n用例执行结束fix2\n') @pytest.mark.usefixtures('test_fixture') @pytest.mark.usefixtures('test_fixture2') def test_b(self): assert 1==1
执行结果
7.skip – 跳过测试
7.1 pytest.skip() 用于函数内,跳过测试用例
@pytest.mark.parametrize('a,b,expect',[(1,1,1),(2,3,5)]) def test_001(self,test1,a,b,expect): pytest.skip('跳过此测试用例') assert a+b==expect
用于函数外,跳过测试用例
@pytest.mark.skip(reason="功能未实现")@pytest.mark.parametrize('a,b,expect',[(1,1,1),(2,3,5)]) def test_001(self,test1,a,b,expect): assert a+b==expect
用在函数外,条件condition为True时,跳过用例
@pytest.mark.skipif(condition=True,reason="功能未实现") @pytest.mark.parametrize('a,b,expect',[(1,1,1),(2,3,5)]) def test_001(self,test1,a,b,expect): assert a+b==expect
8.rerunfailure–失败重跑,插件pytest-rerunfailures
安装
前提条件:pytest (>=5.3)
和python >=3.6
安装:pip install pytest-rerunfailures
查看安装版本:pip show pytest-rerunfailures
pytest-rerunfailures 使用
命令行参数: --reruns n(重新运行次数)–reruns-delay m(等待运行秒数)使用装饰器: @pytest.mark.flaky(reruns=5, reruns_delay=2)命令行实例
#!/usr/bin/env python3# !coding:utf-8import pytestimport randomdef test_simple_assume(): # 每次case运行的值为1或者2,具有随机性 r = random.randint(1, 3) assert r == 1if __name__ == '__main__': pytest.main(['Test_demo01.py', '-v', '--reruns=2', '--reruns-delay 2'])
配置执行次数越多(如--reruns=2),执行的成功率越高。
命令行参数:--reruns n
(重新运行次数),--reruns-delay m
(等待运行秒数)
例如
pytest pytest-demo.py --reruns 3 --reruns-delay 2
装饰器实例
@pytest.mark.flaky(reruns=5, reruns_delay=2) def test_01(self): print('---用例01---') assert 1 == 2
执行结果
9.Mark装饰器之order执行顺序
需要先安装插件
cmd命令窗口:pip install pytest-ordering
在pycharm中File-->settings-->Project-->Python Interpreter-->点击+号-->搜索pytest-ordering安装。
查看安装版本:pip show pytest-ordering
使用方法:
控制用例执行顺序的方法;在需要调整用例执行顺序的函数(或方法)前增加,如@pytest.mark.run(order=x),x表示数字;执行顺序,由小到大、由正到负、未标记的在正数后、负数前执行,顺序为:1,2,3,无标记,-3,-2,-1;实例
import pytestclass Test_Class3(): @pytest.mark.run(order=2) def test_case1(self): print("测试方法1") @pytest.mark.run(order=1) def test_case2(self): print("测试方法2") @pytest.mark.run(order=3) def test_case3(self): print("测试方法3")if __name__ == '__main__': pytest.main(['Test_demo02.py' '-s'])
执行结果
============================= test session starts =============================collecting ... collected 3 itemsTest_demo02.py::Test_Class3::test_case2 PASSED [ 33%]测试方法2Test_demo02.py::Test_Class3::test_case1 PASSED [ 66%]测试方法1Test_demo02.py::Test_Class3::test_case3 PASSED [100%]测试方法3============================== 3 passed in 0.02s ==============================
10.setup、teardown
setup_class()和 teardown_class()函数需要定义在测试类中,定义在类外不起作用。setup_class()定义场景,比如:创建日志对象,创建数据库的连接,创建接口的请求对象等。teardown_class()定义场景,比如:销毁日志对象,销毁数据库的连接,销毁接口的请求对象。
"""函数需要定义在测试类中,定义在类外不起作用。setup_method()和 teardown_method(),在每个测试方法之前/之后执行。定义场景,比如:打开浏览器/关闭浏览器。setup_class()定义场景,比如:创建日志对象,创建数据库的连接,创建接口的请求对象等。teardown_class()定义场景,比如:销毁日志对象,销毁数据库的连接,销毁接口的请求对象。"""import pytestclass Test_setUp_tearDown: # 方法级,前置函数 def setup_method(self): # print("setup_method(self):在每个测试方法之前执行") print("在每个测试方法之前执行") # 方法级,后置函数 def teardown_method(self): # print("teardown_method(self):在每个测试方法之后执行\n") print("在每个测试方法之后执行") # 类级,前置函数 def setup_class(self): # print("setup_class(self):每个测试类之前执行一次\n") print("每个测试类之前执行一次") # 类级,后置函数 def teardown_class(self): # print("teardown_class(self):每个测试类之后执行一次") print("每个测试类之后执行一次") # 测试用例a def test_a(self): print("test_a方法") assert True # 测试用例b def test_b(self): print("test_b方法") assert Trueif __name__ == '__main__': pytest.main()
pytest钩子函数
在pytest中,钩子函数是一种特殊的函数,用于在测试执行过程中的特定阶段插入自定义逻辑。pytest提供了许多内置的钩子函数,这些钩子函数允许您在测试的不同阶段进行自定义操作。以下是pytest中常用的钩子函数及其作用
1.作用在类以外的钩子函数
1 setup()/tear_down()def setup(): print("这是一个setup") def teardown(): print("这是一个teardown")
2 setup_module()/teardown_module()
setup_module():在测试模块开始之前运行,用于设置模块级别的资源或配置。可以在该钩子函数中执行一次性的模块设置。
teardown_module():在测试模块结束之后运行,用于清理模块级别的资源或配置。可以在该钩子函数中执行一次性的模块清理。
def setup_module(self):
print("这个是模块级别的setup_module")
def teardown_module(self):
print("这个是模块级别的teardown_module")
3 setup_function/teardown_function
每条用例执行前执行一次,不会作用于class中的test_case
setup_function():在每个测试函数开始之前运行,用于设置单个测试函数的资源或配置。可以在该钩子函数中执行每个测试函数的准备工作。
teardown_function():在每个测试函数结束之后运行,用于清理单个测试函数的资源或配置。可以在该钩子函数中执行每个测试函数的清理工作。
def setup_function()
print("setup_function模块中每条用例执行前执行一次!")
def teardown_function():
print("teardown_function 模块中每条用例执行后执行一次!")
2.作用在类中的钩子函数
setup_class():在每个测试类开始之前运行,用于设置单个测试类的资源或配置。可以在该钩子函数中执行每个测试类的准备工作。
teardown_class():在每个测试类结束之后运行,用于清理单个测试类的资源或配置。可以在该钩子函数中执行每个测试类的清理工作。
setup_method():在每个测试方法开始之前运行,用于设置单个测试方法的资源或配置。可以在该钩子函数中执行每个测试方法的准备工作。
teardown_method():在每个测试方法结束之后运行,用于清理单个测试方法的资源或配置。可以在该钩子函数中执行每个测试方法的清理工作。
3.pytest_runtest_makereport 钩子函数
pytest_runtest_makereport
是一个重要的钩子,它在测试运行期间被调用,用于生成测试报告。通过覆盖这个钩子,你可以自定义测试报告的行为,例如在测试失败时执行某些操作,或者收集额外的信息。
示例
import pytest@pytest.hookimpl(tryfirst=True, hookwrapper=True)def pytest_runtest_makereport(item, call): # execute all other hooks to get the report object outcome = yield rep = outcome.get_result() # we only look at actual failing test calls, not setup/teardown if rep.when == "call" and rep.failed: # 自定义逻辑:当测试失败时的操作 print(f"Test {item.name} failed") # 你可以在这里添加更多的逻辑,例如记录日志、发送邮件等
在这个示例中,我们使用 @pytest.hookimpl
装饰器定义了一个 pytest_runtest_makereport
钩子函数。参数 tryfirst=True
表示这个钩子应该尽可能早地被调用,而 hookwrapper=True
表示这是一个钩子包装器,它会先执行其他注册的钩子函数,然后执行自己的逻辑。
钩子详解
item
:当前正在运行的测试项。call
:当前测试阶段(setup、call 或 teardown)。rep
:测试报告对象,包含测试的结果信息。 示例中的逻辑
outcome = yield
:这部分代码使用 yield
来执行其他注册的钩子函数,并获取它们的结果。这是 hookwrapper
的典型用法。rep = outcome.get_result()
:获取测试报告对象。条件判断:if rep.when == "call" and rep.failed:
检查当前阶段是否为测试执行阶段(而非 setup 或 teardown),并且测试是否失败。自定义逻辑:在测试失败时执行自定义的逻辑,例如打印一条消息。
完整的插件示例
如果你需要将这个钩子放入一个插件中,可以创建一个 .py
文件(例如 my_plugin.py
),并在其中定义这个钩子:
import pytestclass MyPlugin: @pytest.hookimpl(tryfirst=True, hookwrapper=True) def pytest_runtest_makereport(self, item, call): # execute all other hooks to get the report object outcome = yield rep = outcome.get_result() # we only look at actual failing test calls, not setup/teardown if rep.when == "call" and rep.failed: # 自定义逻辑:当测试失败时的操作 print(f"Test {item.name} failed") # 你可以在这里添加更多的逻辑,例如记录日志、发送邮件等def pytest_configure(config): config.pluginmanager.register(MyPlugin(), "my_plugin")def pytest_unconfigure(config): plugin = getattr(config, "my_plugin", None) if plugin is not None: config.pluginmanager.unregister(plugin)
在这个示例中,我们定义了一个 MyPlugin
类,并在其中实现了 pytest_runtest_makereport
钩子。我们还在 pytest_configure
和 pytest_unconfigure
函数中注册和注销插件。
通过这种方式,你可以轻松地扩展 pytest 的功能,自定义测试报告的行为。
4 pytest_sessionfinish(session, exitstatus) 钩子函数
pytest_sessionfinish
是 pytest 提供的一个钩子(hook),它允许你在整个测试会话结束时执行一些操作。这个钩子在所有测试项运行完毕后被调用,但在此之前所有测试收集、设置(setup)、执行以及清理(teardown)都已经完成。这意味着你可以利用这个钩子来做一些总结性的工作,比如输出一些统计信息,关闭资源,清理环境等。
这个钩子有两个参数:
session: 一个Session
对象,提供了对当前测试会话的信息访问。exitstatus: 一个整数,表示测试会话的退出状态码。这可以用来决定脚本的退出状态,例如是否成功执行。 如何使用 pytest_sessionfinish
下面是一个简单的例子来展示如何使用 pytest_sessionfinish
钩子:
import pytestdef pytest_sessionfinish(session, exitstatus): # 在这里添加你的逻辑 print("All tests have finished.") print(f"Exit status: {exitstatus}") # 你也可以根据 exitstatus 决定做一些不同的事情 if exitstatus == 0: print("All tests passed.") else: print("Some tests did not pass.")
更复杂的用法
如果你想要创建一个插件,可以像这样定义 pytest_sessionfinish
钩子:
import pytestclass MyPlugin: def pytest_sessionfinish(self, session, exitstatus): print("All tests have finished.") print(f"Exit status: {exitstatus}")def pytest_configure(config): # 注册插件 config.pluginmanager.register(MyPlugin(), "my_plugin")def pytest_unconfigure(config): # 清理插件 plugin = getattr(config, "my_plugin", None) if plugin is not None: del config.my_plugin config.pluginmanager.unregister(plugin)
Session 对象
session
参数是一个 pytest.Session
实例,它提供了许多有用的方法和属性来访问关于测试会话的信息。例如,你可以使用 session.items
获取测试项列表,session.testsfailed
获取失败的测试数量,以及其他有用的统计信息。
Exit Status
exitstatus
是一个整数,表示测试会话的退出状态。常见的值包括:
你可以根据这个状态码来决定后续的动作,例如发送通知或更新数据库的状态。
通过使用 pytest_sessionfinish
钩子,你可以确保在测试会话结束后执行一些必要的清理工作或通知操作。
pytest+allure测试报告(图文详解)
0.入口python文件执行pytest程序,并生成报告展示
os.system('python -m pytest --alluredir ./allure --clean-alluredir')time.sleep(3)os.system('allure serve ./allure')
说明:
python -m pytest: -m使用pytest模块执行
--alluredir ./allure:将测试结果保存到 allure
目录中.(或者使用 --alluredir=./allure),是pytest-allure-adaptor插件
中的选项。
--clean-alluredir:在保存测试结果之前先清理 allure 目录,是插件pytest-allure-adaptor
中的选项。
1.安装allure
下载地址:Releases · allure-framework/allure2 · GitHub
解压后,运行 allure.bat,会弹出一个黑框一闪而过(闪的太快,没有截到图~~)
2.配置环境变量
allure安装路径: C:\D\soft\allure-2.26.0\bin path中
cmd中输入allure,allure --version,查看环境变量是否配置成功
3.下载allure-pytest插件
下载 pip install allure-pytest
查看 pip show allure-pytest
4.执行自动化用例,生成allure报告所需文件
执行用例,并指定生成allure报告路径,命令如下:
pytest Test_demo02.py -s -q --alluredir=./result
alluredir 指定存放allure报告的路径
如图,生成两个文件夹:pytest_cache/result
result文件夹中三个json文件,对应用例中的三条case。
5.查看报告方式两种
方式一:可以通过allure解析json文件,使用命令查看报告
allure serve ./result (指定result文件夹路径)
会自动打开浏览器,展示allure报告
方式二:通过结果生成报告
allure generate ./allure -o ./allure-report --clean
./allure: pytest执行测试报告生成的数据文件路径
-o ./allure-report: 输出报告到此路径
--clean:--clean
参数的作用是在生成新报告之前清理目标目录(也就是报告输出目录)。这意味着如果目标目录 ( ./allure-report) 中已经存在旧的报告文件,使用 --clean
参数会先删除这些旧文件,然后再生成新的报告。
打开报告
allure open -h 127.0.0.1 -p 8883 ./report/
6.QA-allure serve ./result/报错的解决思路
pycharm的终端输入:allure serve ./result/
allure报错:‘allure‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件
解决思路:
没有下载allure,只是安装了allure-pytest插件安装allure后,检查是否配置allure的环境变量到path配置环境变量后,需要重启pycharm,可能是环境变量没有同步过来导致。关闭防火墙开关。
7.allure常用注解
实例如下
import allureimport pytestfrom allure_commons.types import LinkType, Severity@allure.parent_suite('我是parent_suite')@allure.suite('我是suite')@allure.sub_suite('我是sub_suite')@allure.epic('我是epic')@allure.feature('我是feature')@allure.story('我是story')class TestAllureDemo: @allure.step('我是断言') def assert_one(self, a, b): assert a == b @allure.id('我是id') @allure.title('我是title') @allure.link('https://www.baidu.com/', LinkType.ISSUE, '我是link_ISSUE') @allure.label('我是label') @allure.issue('https://www.baidu.com/', '我是issue') @allure.description('我是description') @allure.severity(Severity.BLOCKER) @allure.tag('我是tag') @allure.testcase('https://www.baidu.com/', 'testcase') def test_01(self): self.assert_one(1, 1) @allure.id('我是id') @allure.title('我是title') @allure.link('https://www.baidu.com/', LinkType.LINK, '我是link') @allure.label('我是label') @allure.issue('https://www.baidu.com/', '我是issue') @allure.description('我是description') @allure.severity('我是severity') @allure.tag('我是tag') @allure.testcase('https://www.baidu.com/', '我是testcase') def test_02(self): allure.dynamic.mro() allure.dynamic.title('我是修改后的title') allure.dynamic.link('https://www.baidu.com/', LinkType.LINK, '我是修改后的link') allure.dynamic.label('我是修改后的label') allure.dynamic.issue('https://www.baidu.com/', '我是修改后的issue') allure.dynamic.description('我是修改后的description') allure.dynamic.severity('我是修改后的severity') allure.dynamic.tag('我是修改后的tag') allure.dynamic.testcase('https://www.baidu.com/', '我是修改后的testcase') assert 1 > 1
生成的报告如下
case1: test_01
case2: test_02
遇到的问题
问题1:pytest执行的case,如果返回结果有中文,获取数据显示unicode编码格式
原因:是pytest默认将输出报告 视为ASCII字符串,并在测试报告中按原样显示。由于中文字符不属于ASCII字符范围,因此pytest会将其转换为Unicode编码表示。
解码后,pytest的执行报告才能正常显示中文。
使用语句:result = response.content.decode("unicode-escape")
问题2:在pycharm/idea中,执行程序运行:pytest 命令执行用例时, 提示:PermissionError: [Errno 13] Permission denied: 'C:\\Program Files\\Python310\\lib\\site-packages\\comtypes\\gen\\_944DE083_8FB8_45CF_BCB7_C477ACB2F897_0_1_0.py'
Program Files
目录通常是一个系统受保护的目录,在默认情况下,普通用户可能没有足够的权限去读取或修改其中的文件。这可能是为什么 pytest 会遇到权限拒绝的原因。
以下是你可以尝试的一些解决方案:
1. 使用管理员权限运行 pytest
在 Windows 上,你可以尝试以管理员身份运行命令提示符或 PowerShell,然后重新运行你的测试。
打开命令提示符或 PowerShell。
右键点击选择“以管理员身份运行”。
在命令提示符中运行 pytest 命令。
2. 检查 Python 安装路径
如果你的 Python 安装在 Program Files 目录下,考虑将 Python 重装到一个不需要管理员权限的目录,比如 C:\Python310。
3. 虚拟环境
使用虚拟环境可以避免直接在系统级别的 Python 安装上进行操作。这样可以减少权限问题,并且让你的项目依赖更加隔离。
创建一个新的虚拟环境。
激活虚拟环境。
在虚拟环境中安装所需的包。
在虚拟环境中运行你的测试。
问题2.1:为什么之前执行pytest,没有提示权限问题
如果你之前能够正常执行 pytest 测试,而现在出现了权限错误,可能有几个潜在的原因导致这一变化。这里有一些可能性:
系统更新或安全策略变化:
最近是否有过系统更新?有时操作系统或防病毒软件的更新会改变某些文件的权限设置。是否启用了新的安全策略或防火墙规则,限制了对某些文件的访问?Python 版本或依赖库更新:
你是否更新了 Python 版本或comtypes
库?新版本可能改变了某些行为或文件生成的位置。更新依赖库可能导致一些文件的生成位置或方式发生变化,从而影响到权限。 虚拟环境的变化:
如果你在虚拟环境中工作,是否有任何环境的变化?例如激活了不同的虚拟环境,或者虚拟环境的权限设置发生了变化。测试代码的变更:
最近是否修改了测试代码或相关依赖?某些修改可能会触发不同的文件访问逻辑。如果你的测试代码或框架最近有所更新,这些更新可能会引入新的行为或依赖。文件系统缓存或锁定状态:
有时候文件系统的缓存或锁定状态可能导致暂时性的权限问题。重启计算机或清理缓存可能会解决这类问题。并发操作:
如果你的测试是在并发环境下运行的(例如多线程或多进程),其他进程可能正在使用或锁定这些文件。环境变量或配置文件:
检查是否有环境变量或配置文件的更改,这些更改可能会影响文件的访问权限。IDE 或构建工具的更改:
如果你是通过 IDE 或者构建工具(如 Jenkins)运行测试,这些工具的更新或配置更改也可能导致此类问题。结论:由于pycharm中环境变化导致,pytest引用的python环境不是预期的环境,修改为预期环境后,问题解决。
pytest引用错误的python环境为:C:\Program Files\python310,通过控制面板>程序和软件,没有查询到python310,直接手动删除C:\Program Files\python310文件夹后。
在pycharm>termianl,使用pip命令查看,可以正常引用到预期的环境了
C:/Users/sjy/AppData/Local/Programs/Python/Python39/python.exe
此时,执行程序运行:pytest 命令执行用例时,功能正常。
参考文档:pytest框架详解_pytest框架 cllections.abc-CSDN博客