pytest是一个测试框架,功能与unittest类似,完全兼容unittest的功能。一般做接口测试的时候,以前用的多的是python requests httptestrunner完成接口自动化测试与报告生成,看到现在很多都在用pytest框架,我也来学习一下,接口测试中pytest应用广泛的是通过python pytest allure生成测试报告,报告格式比较美观。
文章比较长,先简单概述一下本文的大概内容:
1、环境搭建以及pytest是怎么运行的,如何识别有效用例
2、用例执行顺序、参数传递、数据驱动
3、断言,以及常见的pytest装饰器
4、测试报告生成,包括自带的pytest的报告以及集成allure的报告。
pytest安装
代码语言:javascript复制pip install -U pytest
查看安装版本:
cmd窗口输入:pytest --version ,会在窗口中输出类似下面格式的一句话:
This is pytest version 5.4.3, imported from d:python38libsite-packagespytest__init__.py
用例的识别与运行
用例编写规范
- 测试文件以test_开头(或者以_test结尾) pytest会找当前以及递归查找子文件夹下面所有的test_*.py或*_test.py的文件,把其当作测试文件(除非显式指定文件所在路径)
- 测试类名称以Test开头,并且不能带有init方法 如果类名称以Test开头的class类中包含了init方法,则会触发告警,提示PytestCollectionWarning: cannot collect test class 'TestXXX'
- 测试函数以test_开头
- 断言使用基本的assert即可
运行参数
你们可能会有这样的疑问,现在大家都在用类似pycharm的IDE工具,为什么还要去学习命令行运行的参数和方式呢?
pytest框架是一个测试框架,如果需要集成到jenkins上的话,是需要用命令行的方式去执行的,有时候要执行多个用例的时候,用命令行文件比较方便。
pytest可以在命令行执行,在命令行执行的时候,可以带很多参数,下面介绍几种常用到的参数用法:(使用pytest --help可以看到命令参数的帮助文档)
- 不带参数执行
使用方法:pytest 或者 py.test , 将会读取当前路径下所有符合规则的文件,类,方法,函数全部执行
- -v 参数
打印详细运行的日志信息,方便定位问题
- -s参数
可以在控制台输出结果,当代码中有用到print语句输出信息时,不加这个参数的话,控制台是不会显示print的内容的
- -k参数
使用该参数可以指定运行满足要求的用例。用法如下:
代码语言:javascript复制pytest -k "类名"
pytest -k "方法名"
pytest -k "类名 and not 方法名"
注意: -k参数后面跟的引号只能用双引号"",不能用单引号'',否则不会识别到用例,运行会报错
- -x参数
遇到用例执行失败或断言失败,立即停止运行,不执行后面的用例。
- --maxfail参数
设置允许失败的用例数,超过这个阈值时,停止运行。
pytest --maxfail=num ,失败用例数>=num时,停止运行
- -m参数
按照标签名运行所有包含某个标签的用例,需要在测试用例上面都加上装饰符@pytest.mark.标记名。使用-m选项,可以使表达式指定多个标记名。使用-m "mark1 and mark2"可以同时选中带有这两个标记的所有测试用例。使用-m "mark1 and not mark2"则会选中带mark1标记且不带mark2标记的测试用例,使用-m "mark1 or mark2"则会选中带有mark1或者mark2的所有测试用例。
用例标记使用用法如下:
代码语言:javascript复制import pytest
@pytest.mark.mark1
@pytest.mark.mark2
def test_a002(self):
print('this is test_a002 method')
使用-m参数运行时,有可能会提示
PytestUnknownMarkWarning: Unknown pytest.mark.xxx - is this a typo?
这只是一个告警,不影响实际执行结果。处理方式有以下几种:
方法一:在测试用例文件的根目录新建conftest.py,内容如下:
代码语言:javascript复制# 单标签处理方式
def pytest_configure(config):
config.addinivalue_line(
"markers", "mark1" # test 是标签名
)
# 多标签处理方式
def pytest_configure(config):
marker_list = ["mark1", "mark2"] # 标签名集合
for markers in marker_list:
config.addinivalue_line(
"markers", markers
)
方法二:在项目根路径或者用例目录下新建一个pytest.ini文件,内容如下:
代码语言:javascript复制[pytest]
markers=
mark1
mark2
mark3
或者用如下格式:
[pytest]
markers=
mark1:this is test1 method mark
mark2:this is test2 method mark
mark3:this is test3 method mark
注意:有多个mark的时候,换行写,要注意缩进,不缩进是无法识别的,每个mark名称后面是可以加冒号,然后备注mark的相关详细描述信息。
方法三:在pytest.ini文件中设置告警过滤,这样可以避免由于mark标记使用过多时,要一个一个配置,比较麻烦。
具体使用方法可以参考官方文档:
https://docs.pytest.org/en/latest/warnings.html
代码语言:javascript复制[pytest]
addopts = -p no:warnings
或者:
[pytest]
filterwarnings =
error
ignore::UserWarning
运行模式
pytest提供了多种运行模式,可以指定某个模块,执行单个pytest模块进行调试,也可以单独运行某个类下面的某个测试方法。
命令行运行具体使用方法如下:
代码语言:javascript复制pytest 文件名.py
pytest 文件名.py::类名
pytest 文件名.py::类名::方法名
也可以在pycharm中运行pytest用例
1、先打开Pycharm设置->Tools->Python Integrated Tools->Testing:pytest
(需要安装pytest依赖,然后符合编写规则的测试用例都能被pycharm识别出来,会在用例前面出现一个绿色的执行按钮,点击这个按钮就能执行某个方法或者某个类)
pytest 框架结构
与unittest类似,执行用例前后会执行setup、teardown来增加用例的前置和后置条件。
pytest的前置和后置条件大概有这么几类:
- 模块级(setup_module/teardown_module)
在模块始末调用
- 函数级(setup_function/teardown_function)
在函数始末调用(在类外部)
- 类级(setup_class/teardown_class)
在类始末调用(在类中)
- 方法级(setup_method/teardown_method)
在方法始末调用(在类中)
- 方法级(setup/teardown)
在方法始末调用(在类中)
调用顺序:
setup_module>setup_function>teardown_function>setup_class>setup_method>setup>teardown>teardown_method>teardown_class>teardown_module
注意事项:
1、其中函数级的setup_function/teardown_function是在class类外部调用的,写在class类中是没用的,不会调用
2、(setup_method/teardown) 与 (setup/teardown)功能是一样的,优先级是先执行setup_method,在执行setup。一般二者用其中一个即可.
验证上面的执行顺序,可以执行下面的脚本,
代码语言:javascript复制在一个test开头的py文件里面,编写一下脚本:
def setup_module():
print('n 这是setup_module方法,只执行一次,当有多个测试类的时候使用')
def teardown_module():
print('这是 teardown_module方法,只执行一次,当有多个测试类的时候使用')
def teardown_module():
print('这是 teardown_module方法,只执行一次,当有多个测试类的时候使用')
def setup_function():
print('这是 setup_function方法,只执行一次,当有多个测试类的时候使用')
def teardown_function():
print('这是 teardown_function方法,只执行一次,当有多个测试类的时候使用')
def test_five():
print('this is test_five method')
def test_six():
print('this is test_six method')
class TestPytest01:
def setup_class(self):
print('调用了setup_class1方法')
def teardown_class(self):
print('调用了teardown_class1方法')
def setup_method(self):
print('执行测试方法前的setup1操作')
def teardown_method(self):
print('执行测试方法后的teardown1操作')
def test_one(self):
print('this is test_one method')
def test_two(self):
print('this is test_two method')
def setup(self):
print('this is setup 方法')
def teardown(self):
print('this is teardown 方法')
class TestPytest02:
def setup_class(self):
print('调用了setup_class2方法')
def teardown_class(self):
print('调用了teardown_class2方法')
def setup_method(self):
print('执行测试方法前的setup2操作')
def teardown_method(self):
print('执行测试方法后的teardown2操作')
def test_three(self):
print('this is test_three method')
def test_four(self):
print('this is test_four method')
然后看打印的输出结果:
控制用例的执行顺序
pytest默认的执行顺序是按照文件名以及测试方法名称排序执行的,如果想指定用例的顺序,可以使用pytest-ordering插件,在测试方法前面加上装饰器@pytest.mark.run(order=num),就可以按照num的大小顺序来执行。
安装:
pip install pytest-ordering
案例:
代码语言:javascript复制import pytest
class TestPytest:
@pytest.mark.run(order=-2)
def test_03(self):
print('test_03')
@pytest.mark.run(order=-3)
def test_01(self):
print('test_01')
@pytest.mark.run(order=4)
def test_02(self):
print('test_02')
执行结果如下:
注意:按照num排序时,正整数在前,负数在后面,然后统一按照从小到大的顺序执行。(我目前使用的是pytest5.4.3版本,不排除以后版本更改排序规则)
pytest fixtures
pytest中可以使用@pytest.fixture装饰器来装饰一个方法,被装饰方法的方法名可以作为一个参数传入到测试方法中。可以通过这种方式来完成测试之前的初始化操作,也可以返回数据给测试函数。
代码语言:javascript复制import pytest
class TestFixture:
@pytest.fixture()
def login(self):
return 11
def test_001(self, login):
assert 1 10 ==login
fixture的scope参数:
根据作用范围大小范围划分,分别是:session>module>class>function.
代码语言:javascript复制@pytest.fixture(scope='function') scope的默认值是function
- function函数或者方法级别都会被调用
- class类级别调用一次
- module模块级别调用一次
- session是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module
通过以下脚本可以测试一下scope的作用范围:
通过更改scope的枚举值,即可看到效果,可以看到print('调用login方法')在不同的scope选项下,打印出来的次数是不一样的。
代码语言:javascript复制import pytest
@pytest.fixture(scope='class')
def login():
print('调用login方法')
return 11
class TestFixture:
def test_001(self, login):
print('this is test_001方法')
assert 1 10 ==login
def test_002(self, login):
print('this is test_001方法')
assert 1 10 ==login
class TestFixture1:
def test_003(self, login):
print('this is test_003方法')
assert 1 10 ==loginconftest.py文件
一般用于scope='session'级别的fixture。conftest.py被pytest视为一个本地插件库,使用conftest.py的规则:
1、conftest.py这个文件名是固定的,不可以更改
2、conftest.py与运行用例在同一个包下,并且该包中要有__init__.py文件
3、使用的时候不需要导入conftest.py,pytest会自动加载,放到哪个package下,就在这个package内有效。
fixture的autouse参数:
如果想让每条测试用例都添加fixture功能,那么可以使用@pytest.fixture里面的autouse参数,autouse='true'则会自动应用到所有用例中。
用法如下:
使用fixture传递测试数据
fixture的param参数可以用来传递测试数据,实现数据驱动的效果,避免出现冗余代码。可以大大减少代码量,并且便于阅读和维护。传入的数据需要使用一个固定的参数名request来接收,代码如下:
代码语言:javascript复制import pytest
@pytest.fixture(params={1,2,3})
def data(request):
return request.param
def test_data(data):
print("测试数据{data}")
assert data<10
运行结果:
pytest使用pytest-xdist并行运行测试
pytest-xdist是pytest里面的一个分布式执行的插件,可以多个CPU或主机执行。这个是进程级的并发,需要保证测试用例之间的独立性,插件是动态决定测试用例执行顺序,如果互相之间有依赖,可能会导致执行失败/达不到预期的结果。
安装:pip install pytest-xdist
用法:
pytest -n auto 或者 pytest -n num
参数为auto时,系统会自动检测CPU核数,如果参数为num数字的话,则表示指定运行的处理器进程数量。
pytest使用pytest-html生成简易测试报告
安装:pip install pytest-html
使用方法:pytest --html=xxxx/report.html (通过这种方式,生成的html报告,css文件是独立的,发给其他人的时候要一起带上css样式文件)
pytest --html=xxxx/report.html --self-contained-html (使用self-contained-html 参数,会将css样式文件的内容直接写到html文件中)
生成的报告样式如下:
报告中会包含Environment和Summary以及Results的相关数据,如果想要在Environment和Summary下添加一些个性化的内容展示到报告中的话,可以在conftest.py文件中添加以下代码:
代码语言:javascript复制import pytest
from py._xmlgen import html
# Environment下添加配置项或者修改已有配置项d的值
def pytest_configure(config):
config._metadata['测试地址'] = '192.168.1.XXX'
config._metadata['项目描述'] = '这是XXX项目测试报告'
config._metadata['Python'] = '2.7' #报告中默认会有python版本,可以自己手动修改
# Summary下添加个性化的内容展示
@pytest.mark.optionalhook
def pytest_html_results_summary(prefix, summary, postfix):
prefix.extend([html.p("测试人: 小博")])
加上以上代码后,运行生成的报告如下:
pytest断言
使用过unittest框架的都知道,unittest里面封装了很多的断言方法,有assertEqua、assertNotEqual等好几十个断言的方法,在pytest中,断言直接使用assert关键字就行:
assert xx:判断xx为真
assert not xx:判断xx不为真
assert a in b:判断b是否包含a
assert a == b:判断a等于b
assert a !=b:判断a不等于b
断言要做什么判断,可以自己去定义。也可以在assert后面加上断言失败后的描述信息:
assert a>b,'断言失败,实际结果是a<b'
pytest parametrize参数化
先来看一下parametrize()的方法源码:
def parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None):
- 主要参数说明:
argsnames:参数名,是一个字符串,多个参数名中间可以用逗号分隔
argsvalues:参数对应的值,是由参数组成的列表,列表中有几个元素,就会生成几条用例,参数名和参数值的数量要相等。
indirect:该参数值默认为False,表示argnames就是普通的参数,如果将该值设置为True,则可以用来将参数传入到fixture方法中。
ids:用于标志用例的一个id字段,默认可以不传,会自动用argvalues填充,ids参数可以用来区分测试方法的标识。
scope:声明argnames中参数的作用域,进而影响到测试用例的收集顺序
- parametrize使用方法:
单个参数:
代码语言:javascript复制@pytest.mark.parametrize('a',[1,2,3,4] )
def test_ddt01(a):
assert a<5
输出结果:
多个参数:
代码语言:javascript复制@pytest.mark.parametrize('a,b',[("1 1",2),("1 2",3)])
def test_ddt02(a,b):
assert eval(a) == b
多次使用parametrize的用法:
对同一个方法使用多次@pytest.mark.parametrize装饰器
代码语言:javascript复制@pytest.mark.parametrize('a',[1,2])
@pytest.mark.parametrize('b',[1,2])
def test_ddt03(a,b):
print(f'数据组合 a:{a}, b:{b}')
ids参数用法及效果:
代码语言:javascript复制@pytest.mark.parametrize('a',[1,2,3 ],ids=('id-1','id-2','id-3' ))
def test_ddt04(a):
assert a<5
indirect用法:
使用indirectTrue,pytest可以实现将参数传入到fixture方法中,也可以在当前测试用例中使用。
代码语言:javascript复制@pytest.fixture(scope='module')
def fun_a(request):
print(f'fun_a:{request.param}')
return request.param
# indirect=True 声明fun_a是个函数
@pytest.mark.parametrize('fun_a',[9,8,7] ,indirect=True)
def test_ddt05(fun_a):
print(f'a:{fun_a}')
assert fun_a<10
scope参数用法及结果演示:
代码语言:javascript复制import pytest
@pytest.mark.parametrize('test_input, expected', [(1, 2), (3, 4),(5,6)], scope='module')
def test_scope1(test_input, expected):
pass
@pytest.mark.parametrize('test_input, expected', [(1, 2), (3, 4),(5,6)], scope='module')
def test_scope2(test_input, expected):
pass
pytest结合YAML实现数据驱动
在实际测试工作中,通常需要对多组不同饿的输入数据,进行同样的测试操作步骤,可以将多组测试数据以数据驱动的形式注入,可以做到测试数据和测试用例分别进行管理。常见的外部数据源可以用YAML、Json、Excel、CSV等方式进行管理。
下面以YAML为例,简单演示一下如何实现数据驱动:
安装: pip install PyYAML
案例:
创建一个testdata的文件夹,在下面创建data.yml和test_yaml.py文件,内容如下:
代码语言:javascript复制data.yaml:
-
- 1
- 2
-
- 20
- 30
代码语言:javascript复制test_yaml.py:
import pytest
import yaml
@pytest.mark.parametrize('a,b',yaml.safe_load(open('data.yml',encoding='utf-8')))
def test_add(a,b):
print(f'a b = {a b}')
输出结果:
pytest结合allure生成测试报告
Allure框架是一种灵活的、轻量级、支持多语言的测试报告工具,报告美观清晰、一目了然。同时支持多种语言,包括Java、Python、JavaScript、Ruby、Groovy、PHP、.Net、Scala等。
环境搭建:
1、以windows系统为例(先安装好JDK并配置环境变量),先下载allure的命令行工具进行安装。下载地址可从github上进行下载:
https://github.com/allure-framework/allure2/releases
下载最新的安装包后,解压,配置环境变量。
新建一个ALLURE_HOME的环境变量,value指向解压后的根路径,,我电脑上的是:G:devopsallure-2.9.0
然后在PATH中加入%ALLURE_HOME%bin
之所以要单独配置解压后的路径为ALLURE_HOME,是为了以后更换版本后更改环境变量比较方便。
配置好后,在cmd窗口输入 allure --version 会打印出安装的版本。
2、安装python的allure-pytest插件
pip install -U allure-pytest
具体使用方法:
步骤一:在会用pytest执行用例的时候,指定参数 --allure选项及结果数据保存的目录:
pytest --alluredir=./tempdir/data
pytest --alluredir=./tempdir/data --clean-alluredir
加--clean-alluredir选项会先清理数据目录,再重新生成新的数据,不清理也不会影响报告的生成。
步骤二:
- 使用allure serve 打开报告:
在cmd窗口输入allure serve ./tempdir/data ,就会自动打开浏览器显示报告:
- 使用allure ganerate命令生成html格式报告
cmd窗口输入如下命令:
allure generate ./tempdir/data -o ./report --clean
命令说明:
./tempdir/data 指测试数据目录, ./report 指html报告生成的位置, --clean指先清空测试报告目录再重新生成新的测试报告。
需要使用下面的命令打开报告,直接打开html文件,看不到数据:
allure open -h 127.0.01 -p 8088 ./report/
到此,allure报告就生成了,至于报告怎么去分析和查看,可以将报告切换为中文版本自己去进行分析即可。
以上就是pytest常见的一些用法,适合新手入门了解,后续有时间会继续补充pytest的一些其他语法和用法以及扩展功能,欢迎关注小编,能及时获取下次更新喔!
如果觉得这篇文章对你有帮助,请分享给身边的朋友一起学习,谢谢!