pytest
?是一个功能强大且易于使用的 Python 测试框架,用于编写单元测试、集成测试和功能测试。它提供了丰富的功能和灵活的用法,使得编写和运行测试变得简单而高效。
--------------->>>>>
pytest框架优点:
? 简单易用:
pytest
?的语法非常简洁清晰,对于编写测试用例来说非常友好,几乎可以在几分钟内上手。? 自动发现测试:
pytest
?能够自动发现项目中的测试文件和测试函数,无需手动编写繁琐的配置。? 丰富的断言库:
pytest
?内置了丰富的断言库,可以轻松地进行测试结果的判断。? 支持参数化测试:
pytest
?支持参数化测试,能够快速地对多组输入进行测试。? 插件丰富:
pytest
?有着丰富的插件生态系统,可以通过插件扩展各种功能,比如覆盖率测试、测试报告生成等。
可以在pychram中手动安装,也可以使用如下pip命令安装:
pip install pytest
?效验是否安装成功,可以使用如下命令:
pytest --version
终端看到一个版本号 说明安装成功了
pytest由两部分组成:
? 用例主体部分(通常单独放在一个py文件):主体部分写测试用例
? 用例运行语句(通常放在一个main文件):执行测试用例
一个简单的示例如下:
用例主体文件:test_lesson1.py
"""
用例主体部分:
定义1个用例的函数,需要带上test关键字
与之前的函数不同的是:在pytest框架下,可以不写调用语句,也可以执行函数的内容
"""
#主体例子1:
def test_case():
print("用例被运行")
#主体例子2:烤鸭1.0
def test_duck():
print("-----烤鸭店利润计算器开始?作------")
price1 = int(input("请输?烤鸭的进货价:")) # input传递来的值,都是str
price2 = int(input("请输?烤鸭的售卖价:"))
num = int(input("请输?今天卖出的烤鸭数量:"))
result = (price2 - price1) * num
print("今天的烤鸭利润是{}元".format(result))
用例执行文件:main.py
pytest 通过一个list接受参数,所以调用main函数时()里加上[]
#用例执行语句:
import pytest
pytest.main(["-s"])
运行main.py文件,执行结果如下:
-s :
表示开启终端交互,其作?是可以让打印的内容输出显示在终端中,或者可以在终端中与?例中的输?操作进?交互
例如上面的代码--用例2,是通过输入输出完成代码运行的,我们看看不加-s的效果:?
-v:
表示详细输出更详细的输出,包括每个测试?例的详细结果和其他相关信息,例如测试?例所在的模块、?件路径等。
还是上面的代码,加上-v后的终端显示效果:?
在 pytest 中,有关测试?例和测试?件的命名规则有?些常?的约定。
以下是 pytest 的命名规则的?些建议和最佳实践:
? 测试?件的命名:
● 测试?件应以 "test_" 开头或以 "_test" 结尾。
这样 pytest 可以?动识别并执?这些?件中的测试?例。
例如: test_login.py 或 user_test.py 。
--->>
● 使?有意义的名称来描述被测试的功能或模块。
例如: test_authentication.py 或 test_user_management.py 。
--------------------------------->>>
? 测试?例的命名:
● 测试?例函数应以 "test" 开头。这样 pytest 可以?动识别并执?这些函数作为测试?例。
例如: def test_login_success(): 或 def test_add_product_to_cart(): 。
--->>
● 建议使?下划线 _ 分隔单词,?不是使?空格或其他特殊字符。例如: def test_calculate_total_amount(): 或 def test_validate_user_permissions(): 。
--->>
● 使?有意义的名称来描述测试的?的和预期结果。
例如: test_login_success() 测试登录功能的成功场景, test_calculate_total_amount() 测试计算总?额的功能。
--------------------------------->>>
? 测试类的命名(可选):
● 如果使?类来组织测试?例,可以使?驼峰命名法(CamelCase)为测试类命名,且开头需为Test开头。例如: class TestLogin: 或 class TestUserManagement: 。
--->>
● 测试类中的测试?例函数仍然应遵循以 "test" 开头的命名规则。
第一步:当你运行了这段指令之后,它就会在你当前运行指令的文件相对同级目录中 找符合test_开头 或者test_结尾的py文件;或者再往下层找 第二步:然后再找文件中test开头的函数,或者先找Test开头的类,再找类中Test开头的函数; 然后将通过上述方案中找到的所有用例依次执行;
本地运行示例如下:
m2文件夹下有2个py文件、一个main执行文件
与这两个py文件目录同级的还有一个‘testcase’文件夹,里面还有2个py文件
相当于是通过一个main执行文件同时执行m2文件夹所有的py文件中的测试用例
运行main.py文件,终端显示的执行顺序如下:?
上面已经学习过main文件中使用main函数来执行用例,main函数接收的两参数,以及不同参数执行后,终端展示内容的不同;
如果我们有多个用例的py文件、多条用例,看运行的情况的总概述:可以不传参数
当一个用例的项目中,有很多用例的py文件时,可以把main文件写在项目的根目录下
就可以通过main函数的执行,依次执行所有py文件的用例
例如:我的项目文件名是m3,里面有子目录文件?,把main文件放在m3的下级目录中就可以
执行后的终端显示:
换成 pytest.main(["-s"])执行后的终端显示:
快速定位到自己要执行的用例目录下的终端:
方式1:点击要执行的项目文件夹右键 --> Open In -->?Terminal
方式2:使用cd命令,进入要执行的用例目录
除了用main文件运行用例,还可以在Terminal终端用命令来执行用例:
● 终端输入pytest:同等于在main文件的pytest.main([]) ● 终端输入pytest -s:同等于在main文件的pytest.main(["-s"]) ● 终端输入pytest -s -v:同等于在main文件的pytest.main(["-s","-v"]);终端的空格 = 逗号 ● 终端输入pytest -sv:同等于在main文件的pytest.main(["-sv"]); -s -v的缩写 ● 终端输入pytest -vs:同等于在main文件的pytest.main(["-vs"]); -s -v的缩写 -------------->>>> ● 指定用例运行: 终端输入pytest test_01.py::Test01::test001 -sv:同等于在main文件的pytest.main(["test_01.py::Test01::test001", "-vs"]);详解看下面指定运行介绍
终端输入pytest执行效果:?
终端输入pytest -s执行效果:
终端输入pytest -s -v执行效果:
-s -v可以缩写成 -sv,或者-vs,v和s不分先后:
像上面的m3用例目录下,有很多不同的模块,当我们只需要执行某一模块的用例、或者某一个模块的某个类、或者某个类下面的某个用例,就可以进行指定:
① 指定运行其中一个模块的用例:
pytest 模块名
如果需要显示输入内容,再加上-s即可:
pytest 模块名 -s
② 指定运行某个模块中的某个类:
pytest 模块名::函数名/类名 #(双冒号表示模块中的下一层)
③ 指定运行某个模块中的某个类下面的某个用例:
pytest 模块名::函数名::类名
指定m3文件夹下 -> test_01这个模块 -> Test01这个类 -> 下面的test002这个用例
在main文件中指定模块下类下面的某个用例:
写法和终端差不多,只是多了标识符、双引号、逗号
④ 指定文件夹运行用例:
pytest.main(["文件名","-sv"])
指定m3文件夹下 -> testcase这个文件夹下的所有用例
指定文件夹下的子级文件的用例运行:
pytest.main(["文件名/子文件夹名","-sv"])
指定文件夹下 -> 子级文件 - > 模块中的某个用例运行:
pytest.main(["文件名/子文件夹名/模块名::用例名","-sv"])
筛选用例,一般以用例名作为筛选条件,配合筛选关键词“-k”使用即可
例如:筛选用例名包含?040的用例执行
pytest.main(["-k","040","-vs"]) # main文件的写法
pytest -k 040 -vs # 终端命令行的写法
在pytest 中,可以使? setup 和 teardown 函数来定义测试?例的前置和后置操作。
? setup 函数:它会在每个测试?例执?之前运?,?于设置测试环境和准备测试数据。
? teardown 函数:它会在每个测试?例执?之后运?,?于清理测试环境和资源。
------------->>>
setup全称:setup_moduleteardown全称:teardown_moduleps:
● 代码中尽可能前置、后置写统一,要么都带上module,要么都不写
●?setup、teardown 与setup_module、teardown_module同时出现,会导致前置和后置竞争,导致可能其中一个不会在终端显示出来。
一个简单的示例如下:?
def setup():
# 前置操作
print("执行前置操作")
def teardown():
# 后置操作
print("执行后置操作")
def test_example():
# 测试用例
print("执行测试用例")
执行顺序:
先执行setup
再执行test_example
最后执行teardown?
setup_function:函数级别的前置操作(函数即用例)teardown_function:函数级别的后置操作(函数即用例)函数级别的前置、后置操作,每一个用例开始执行的的前后都会执行一次前、后置操作
# test_002.py
def setup_module(): # ---> setup
# 前置操作
print("执行模块级别前置操作")
def teardown_module(): # ---> teardown
# 后置操作
print("执行模块级别后置操作")
def test_example1():
# 测试用例1
print("执行测试用例test_example1")
def test_example2():
# 测试用例2
print("执行测试用例test_example2")
def setup_function():
# 函数级别的前置操作
print("执行函数级别的前置操作")
def teardown_function():
# 函数级别的后置操作
print("执行函数级别的后置操作")
?执行结果:
● setup_class:类级别的前置操作
● teardown_class:类级别的后置操作
setup_class 和 teardown_class :在测试类的开始和结束时运?
但是需要加上默认的‘@classmethod’装饰器,才会被识别为类级别的前、后置操作
------------>>>
类级别的前、后置操作固定写法格式如下:
# file name:test003.py
class TestClass:
@classmethod
def setup_class(cls):
# 类级别的前置操作
print("执行类级别的前置操作")
@classmethod
def teardown_class(cls):
# 类级别的后置操作
print("执行类级别的后置操作")
def test0021(self):
print("执行测试用例test0021")
def test0022(self):
print("执行测试用例test0022")
● setup_method:方法级别的前置操作
● teardown_method:方法级别的后置操作
setup_method 和 teardown_method :在每个测试?法的开始和结束时运 ?
# file name:test_004.py
class TestClass:
def setup_method(self, method):
# 方法级别的前置操作
print("执行方法级别的前置操作")
def teardown_method(self, method):
# 方法级别的后置操作
print("执行方法级别的后置操作")
def test_example1(self):
# 测试用例1
print("执行测试用例test_example1")
def test_example2(self):
# 测试用例2
print("执行测试用例test_example2")
def test_example3(self):
# 测试用例3
print("执行测试用例test_example3")
类级别的前、后置,与方法级别的前、后置一起使用,执行顺序:
先执行类级别的前置操作
再逐个执行每个测试用例,执行每个用例的同时都会执行一次方法级别的前、后置操作
最后执行类级别的后置操作
# file name:test_005.py
class TestClass:
def setup_method(self, method):
# 方法级别的前置操作
print("执行方法级别的前置操作")
def teardown_method(self, method):
# 方法级别的后置操作
print("执行方法级别的后置操作")
def test_example1(self):
# 测试用例1
print("执行测试用例test_example1")
def test_example2(self):
# 测试用例2
print("执行测试用例test_example2")
def test_example3(self):
# 测试用例3
print("执行测试用例test_example3")
@classmethod
def setup_class(cls):
# 类级别的前置操作
print("执行类级别的前置操作")
@classmethod
def teardown_class(cls):
# 类级别的后置操作
print("执行类级别的后置操作")
# file name:test_duck40.py(烤鸭店4.0版本)
def setup():
# 模块的前置操作,用例开始执行之前先打印出一句开始的提示语
print("---烤鸭店系统开始工作---")
def teardown():
# 模块的后置操作,用例执行结束后,打印出一句结束的提示语
print("---即将退出烤鸭店系统---")
def test_duck_4_0():
projects = {} # 装多个商品
while True:
oper_type = input("1 - 录入商品\n2 - 查询商品\n3 - 退出\n请做出你的选择:")
if oper_type == "1":
pro_list = []
# 录入逻辑
print(">>>准备开始录入商品<<<")
load_txt = ["请输入商品名:", "请输入商品的成本价:", "请输入商品的产地:", "请输入商品的生产日期:"]
for i in load_txt:
name1 = input("{}".format(i))
pro_list.append(name1)
projects[pro_list[0]] = pro_list
elif oper_type == "2":
# 查询逻辑
print(projects)
po_name = input("请输入要查询的商品名:")
print("你要查询的商品是:{}".format(projects[po_name]))
# 烤鸭 -- 直接给我烤鸭信息
elif oper_type == "3":
break
else:
print("无法别操作,请重新输入")
continue
import pytest
pytest.main(["-k", "test_duck40", "-vs"])
main文件执行结果:
在Pytest中,标签(也称为标记)是?种?于对测试?例进?分类或分组的机制。
通过为测试?例添加标签,可以在运?测试时选择性地执?特定标签的测试?例,从? ?便地控制测试的范围和?标。
可以理解为:标签也是用来做过滤的一种方式。
------------------------------------------------------------------------->>>
Pytest的标签功能基于装饰器来实现。
可以使? @pytest.mark 装饰器为测试函数或测试类添加标签。
常?的?法是在测试函数或测试类上?添加装饰器,并指定相应的标签。
------------------------------------------------------------------------->>>
mark后不同关键字的含义:
--------->>>
@pytest.mark.skip:标记测试函数为跳过测试,并提供跳过的原因。这在你想要暂时跳过某些测试的情况下很有用,比如某个功能尚未实现或者有缺陷需要修复。
--------->>>
@pytest.mark.xfail:标记预期失败的测试。这表示你预期该测试会失败,但你想要运行它并收集结果。你可以提供失败的原因。
--------->>>
@pytest.mark.parametrize
:使用参数化标记来标记测试函数,并提供参数化的值。这样测试函数将会根据提供的参数值运行多次,每次运行时使用不同的参数值。--------->>>
@pytest.mark.somke:通常用于标记快速运行、覆盖基本功能、且对整体系统影响较小的测试。Smoke 测试旨在快速验证系统的基本功能是否正常,通常在每次代码提交后都会运行,以便快速发现潜在的问题。
--------->>>
@pytest.mark.slow:?通常用于标记运行较慢、覆盖范围较广、对整体系统影响较大的测试。Slow 测试可能包括集成测试、端到端测试、性能测试等,需要更长的时间来执行。
--------->>>
自定义标签:
@pytest.mark.任意标签内容:但是不能有中文、不能以数字开头;自定义标签mu
仅运行文件中包含somke标签的测试用例
# file name:test001.py
import pytest
@pytest.mark.smoke
def test_login():
# 测试登录功能
assert True
@pytest.mark.regression
def test_registration():
# 测试注册功能
assert True
@pytest.mark.smoke
def test_add_to_cart():
# 测试添加到购物车功能
assert True
@pytest.mark.regression
def test_checkout():
# 测试结账功能
assert True
@pytest.mark.skip #标记为跳过的用例
def test001():
print("001")
@pytest.mark.hailey
def test_test_002():
print("添加一个标签后然后运行")
somke执行:
pytest -vs -m smoke # 终端命令行的写法
pytest.main(["-vs", "-m", "smoke"]) # main文件的写法
跳过标记为skip的测试用例(不执行),源码还是使用上面的'test001.py'代码
源码还是使用上面的'test001.py'代码,指定自定义标签的用例运行,用例执行:
pytest -vs -m hailey # 终端命令行的写法
pytest.main(["-vs", "-m", "hailey"]) # main文件的写法
but,执行后报错了:
配置pytest.ini ?件
在项?根?录下的 pytest.ini ?件中可以指定全局标记,以应?于整个项?:
iniCopy code
[pytest] [pytest]
markers =
smoke: Run smoke tests
regression: Run regression tests
hailey: 这是对标签的解释
配置完成后,重新执行一下:
逻辑分为四种:是、非、或、和 是:pytest.main(["-vs", "-m", "hailey"]) -->> pytest -m 'hailey' -vs 非:pytest.main(["-m", "not hailey", "-vs"]) -->> pytest -m 'not hailey' -vs 或:pytest.main(["-vs", "-m", "hailey or smoke"]) ????????终端:-->> pytest -m 'hailey or smoke' -vs 和:pytest.main(["-vs", "-m", "hailey and smoke"]) ????????终端:-->> pytest -m 'hailey and smoke' -vs以下示例,仍使用上面的'test001.py'代码
but,执行报错了?
和的逻辑是,必须同时满足两个或多个条件,所以给自定义标签的这条用例上再加上smoke标签;
同一条用例,可以标记多个标签
再运行即可:
当使? Pytest 进?测试时, conftest.py 是?个特殊的?件,?于管理测试?例中 需要通?数据传递。
下?是?些简单的例?来说明 conftest.py 的使?。
定义?个简单的夹具(fixture):
# file name:conftest.py
import pytest
@pytest.fixture
def setup_data():
return [1, 2, 3, 4, 5]
在 conftest.py 中定义了?个名为 setup_data 的夹具。
这个夹具可以在测试用例的模块(test_001.py)中被使?,如下所示:
# file name:test_001.py
def test_sum(setup_data): #这里接收的参数是ttconftest.py 中定义的函数
#断言求和的值等于15
assert sum(setup_data) == 15
def test_traversal(setup_data): #这里接收的参数也是ttconftest.py 中定义的函数
#断言i对应的数字变量存在于setup_data函数中
for i in setup_data:
assert i in setup_data
然后执行上面的两条测试用例(test_001.py):
通过上面代码的示例中,我们可以看出:
pytest.fixture 是 Pytest 测试框架中的?个装饰器,?于定义测试?例中需要共 享的资源、数据或设置的函数。
它可以在测试?例执?之前、之后或在每个测试?例 之前、之后运?,并提供?种?便的?式来管理测试?例的前置条件和后置操作。
---------->>>
使? pytest.fixture 装饰器,可以创建?个被 Pytest ?动调?的函数,该函数可以为测试?例提供所需的初始化步骤或共享的数据。这个函数可以返回?个值,该值将作为参数传递给测试?例函数。
通过使? pytest.fixture ,目的是为了更?便地管理测试?例的前置条件和后置操 作,实现测试数据的共享和代码的重?,从?使测试代码更加简洁、可维护和可扩展。
pytest.fixture 装饰器可以接受?些参数,?于配置和定制 fixture 函数的?为。
以下是?些常?的参数:
? scope :指定 fixture 的作?域。可以设置为以下?个值之?:
????????● function?(默认值):每个测试函数都会调??次 fixture(与前置一样)
????????● class?:每个测试用例的类都会调??次 fixture。
????????● module?:每个测试模块都会调??次 fixture。
????????● session:整个测试会话只会调??次 fixture。
????????● yield:每个测试函数都会调??次 fixture(与后置一样)
? params :参数化 fixture,可以根据不同的参数值?成多个独?的 fixture 实例。 可以传递?个可迭代对象,每个元素都会作为参数值调? fixture。
? autouse :?动使? fixture,?需在测试函数中显式声明使?该 fixture。可以 设置为 True 或 False 。
??name :为 fixture 指定?个?定义名称,?于在测试报告和?志中标识 fixture。
? 其他参数:可以根据需要添加其他?定义参数,供 fixture 函数使?。
基于一条用例,设置自动调用前置、后置
# file name:conftest.py
import pytest
@pytest.fixture
def data():
return [1, 2, 3, 4, 5, 6]
@pytest.fixture(autouse=True) # autouse自动调用
def auto_print():
print("这里是自动调用的前置")
yield
print("这里是自动调用的后置")
# 测试用例file name:test_002.py
def test00(data):
print(data)
执行结果:
用例:
# 测试用例file name:test_002.py
def test00(data):
print(data)
def test01():
print("用例01运行了")
def test02():
print("用例02运行了")
基于多条用例的执行效果:
可以理解为是基于用例级别的自动调用前、后置操作?
基于模块级别设置为自动调用前置、后置,设置参数为scope = 'module'即可
# file name:conftest.py
import pytest
@pytest.fixture
def data():
return [1, 2, 3, 4, 5, 6]
@pytest.fixture(autouse=True,scope='module') #设置自动调用、且为模块级别
def auto_print():
print("这里是自动调用的前置")
yield
print("这里是自动调用的后置")
执行效果:?
可以理解为:与上面提到的模块级别的setup 和 teardown函数的作用是一样的(1个模块)
基于用例项目根目录下,多个模块执行的效果(两个模块,共6条用例):
基于文件级别设置为自动调用前置、后置,设置参数为scope = 'session'即可
文件级别:针对的是用例根目录下的所有用例模块
file name:conftest.py
import pytest
@pytest.fixture
def data():
return [1, 2, 3, 4, 5, 6]
@pytest.fixture(autouse=True,scope='session') #设置自动调用、且为文件级别
def auto_print():
print("这里是自动调用的前置")
yield
print("这里是自动调用的后置")
还是两个模块:?
执行效果:
基于类级别设置为自动调用前置、后置,设置参数为scope = 'class'即可
但除了声明的类,函数也同样会执行
# file name:conftest.py
import pytest
@pytest.fixture
def data():
return [1, 2, 3, 4, 5, 6]
@pytest.fixture(autouse=True,scope='class') #设置自动调用前、后置,且级别为类
def auto_print():
print("这里是自动调用的前置")
yield
print("这里是自动调用的后置")
执行效果:
在conftest文件中执行参数,传给对应模块的用例
# file name:conftest.py
import pytest
@pytest.fixture(params=["a", "b", "c"])
def params(request):
return request.param
# 用例file name:test_002.py
def test_(params):
print(params)
执行结果:
也可以给参数自定义名称:
在上面的'@pytest.mark.自定义标签'这里有提到pytest.ini文件的配置,以下补充ini文件的其他用途以及总结:
●?ini文件的作用域:针对的是用例根目录下的所有模块
● 文件名:必须是pytest.ini(新建文件时,选择类型为File)
● 配置文件中都有固定的写法,必须遵从
上面的标签中有介绍,不定义的话,执行时找不到会报错
iniCopy code
[pytest] [pytest]
markers =
smoke: Run smoke tests
regression: Run regression tests
hailey: 这是对标签的解释
当我们使用终端命令行来执行时,需要每次都写‘pytest -v -s’,是不行比较麻烦
这时候,就可以在ini文件中配置默认执行的命令:
[pytest]
addopts = -v -s
当我们在pytest.ini.py中配置了默认命令后,main文件中就不用再写['-s' ,'-v']?
执行用例时会默认带上'-s' ,'-v',终端也可以显示出明显信息:
终端运行也是,只要输入一个pytest就可以:
[pytest]
addopts = -v -s -k test_001 # 配置筛选模块
?
[pytest]
addopts = -v -s -k test001
[pytest]
testpaths = tests/
addopts = -v -s
上面有学习到,pytest的模块名、用例名、类名、函数名,都有指定的默认命名规则,如果没有按照命名规则命名,执行用例的时候就会找不到报错;
那么当我们自定义了模块名、用例名、类名、函数名,想要Python也能够正常运行,就可以在ini文件中进行自定义:
[pytest]
python_files = t_*.py # 定义模块名为t_开头
python_classes = T* # 定义类名为T开头
python_functions = t_* # 定义用例名为t_开头
addopts = -v -s
配置完成后,执行正常输出:
比如,我用一个模块中自定了一个函数
# file name:t_0.py
import pytest
@pytest.mark.func
def t_0():
print("t_0用例被运行")
没有在ini文件中配置的时候,运行会有一个警告信息:
不想看见警告信息
处理方式1:在ini文件中声明markers函数
[pytest]
python_files = t_*.py
python_classes = T*
python_functions = t_*
markers =
func:
addopts = -v -s
在执行,就不会显示警告信息了:
处理方式2:在ini文件中将pytest的警告信息禁用
[pytest]
python_files = t_*.py
python_classes = T*
python_functions = t_*
; markers =
; func:
addopts = --disable-warnings -v -s
再执行,也不会显示警告信息:区别是,禁用后会统计警告的数量,但不显示警告信息