首页 > 技术文章 > 一篇文章为你揭秘pytest的基本用法

xiaobotester 2020-07-29 01:15 原文

该文章是从我个人的微信公众号上复制过来的,因此有些图片不能正常显示 ,懒得去一个个重新处理图片了,大家可以点击微信公众号的链接查看也是一样的:

 

 

 

一篇文章为你揭秘pytest

 

 

 

pytest是一个测试框架,功能与unittest类似,完全兼容unittest的功能。一般做接口测试的时候,以前用的多的是python+requests+httptestrunner完成接口自动化测试与报告生成,看到现在很多都在用pytest框架,我也来学习一下,接口测试中pytest应用广泛的是通过python+pytest+allure生成测试报告,报告格式比较美观。

 

文章比较长,先简单概述一下本文的大概内容:

1、环境搭建以及pytest是怎么运行的,如何识别有效用例

2、用例执行顺序、参数传递、数据驱动

3、断言,以及常见的pytest装饰器

4、测试报告生成,包括自带的pytest的报告以及集成allure的报告。

 

pytest安装

pip install -U pytest

查看安装版本:

cmd窗口输入:pytest --version ,会在窗口中输出类似下面格式的一句话:

This is pytest version 5.4.3, imported from d:\python38\lib\site-packages\pytest\__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参数

使用该参数可以指定运行满足要求的用例。用法如下:

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的所有测试用例。

用例标记使用用法如下:

import pytest@pytest.mark.mark1@pytest.mark.mark2def test_a002(self):    print('this is test_a002 method')

使用-m参数运行时,有可能会提示

PytestUnknownMarkWarning: Unknown pytest.mark.xxx - is this a typo?

这只是一个告警,不影响实际执行结果。处理方式有以下几种:

方法一:在测试用例文件的根目录新建conftest.py,内容如下:


# 单标签处理方式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文件,内容如下:

[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

[pytest]addopts = -p no:warnings或者:[pytest]filterwarnings =    error    ignore::UserWarning

 

 

运行模式

pytest提供了多种运行模式,可以指定某个模块,执行单个pytest模块进行调试,也可以单独运行某个类下面的某个测试方法。

命令行运行具体使用方法如下:

pytest 文件名.pypytest 文件名.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。一般二者用其中一个即可.

验证上面的执行顺序,可以执行下面的脚本,

在一个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

案例:

import pytestclass 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装饰器来装饰一个方法,被装饰方法的方法名可以作为一个参数传入到测试方法中。可以通过这种方式来完成测试之前的初始化操作,也可以返回数据给测试函数。

import pytestclass TestFixture:    @pytest.fixture()    def login(self):        return 11
def test_001(self, login): assert 1+10 ==login

 

fixture的scope参数:

根据作用范围大小范围划分,分别是:session>module>class>function.

@pytest.fixture(scope='function') scope的默认值是function
  • function函数或者方法级别都会被调用

  • class类级别调用一次

  • module模块级别调用一次

  • session是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module

通过以下脚本可以测试一下scope的作用范围:

通过更改scope的枚举值,即可看到效果,可以看到print('调用login方法')在不同的scope选项下,打印出来的次数是不一样的。

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 ==login

conftest.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来接收,代码如下:

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文件中添加以下代码:

import pytestfrom 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.optionalhookdef 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使用方法:

单个参数:

@pytest.mark.parametrize('a',[1,2,3,4] )def test_ddt01(a):    assert a<5

输出结果:

 

多个参数:

@pytest.mark.parametrize('a,b',[("1+1",2),("1+2",3)])def test_ddt02(a,b):    assert eval(a) == b

 

多次使用parametrize的用法:

对同一个方法使用多次@pytest.mark.parametrize装饰器

@pytest.mark.parametrize('a',[1,2])@pytest.mark.parametrize('b',[1,2])def test_ddt03(a,b):    print(f'数据组合 a:{a}, b:{b}')

ids参数用法及效果:

@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方法中,也可以在当前测试用例中使用。

@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参数用法及结果演示:

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文件,内容如下:

data.yaml:-  - 1  - 2-  - 20  - 30
test_yaml.py:import pytestimport 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

github如果下载过慢的话,可以从我的网盘里面下载:

链接:https://pan.baidu.com/s/1CJvL_jJuJhFAFP0olUnlCA  

提取码:1234

下载最新的安装包后,解压,配置环境变量。

新建一个ALLURE_HOME的环境变量,value指向解压后的根路径,,我电脑上的是:G:\devops\allure-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的一些其他语法和用法以及扩展功能,欢迎关注小编,能及时获取下次更新喔!

 

如果觉得这篇文章对你有帮助,请分享给身边的朋友一起学习,谢谢!

推荐阅读