一、pytest介绍
pytest是一个非常成熟的全功能的Python测试框架,主要特点有以下几点: 1、简单灵活,容易上手,文档丰富; 2、支持参数化,可以细粒度地控制要测试的测试用例; 3、能够支持简单的单元测试和复杂的功能测试,还可以用来做;selenium/appnium等自动化测试、接口自动化测试pytest+requests; 4、pytest具有很多第三方插件,并且可以自定义扩展;
二、用例默认编写规则
- 测试文件默认以test_开头(以_test结尾也可以);
- 测试类默认以Test开头,并且不能带有 init 方法;
- 测试函数默认以test_开头;
- 断言使用基本的assert即可;
三、用例分类
- 通过自带的装饰器@pytest.mark来标记测试用例;
- 执行的时候带上参数"-m"即可只运行标记的类和方法;
3.1、标记步骤
3.1.1、注册标签名
通过 pytest.ini 配置文件注册,在文件当中的添加如下 [pytest] markers= ? ? ? ? ? ? ? ? 标记:标记说明 ? ? ? ? ? ? ? ? …
3.1.2、给用例打标记
注意:pytestmark是固定的
-
可以标记文件,文件下的所有用例都会打上标记 在文件下全局变量处打上标记pytestmark = [pytest.mark.标签1,pytest.mark.标签2] -
可以标记测试类,类型的所有用例都会打上标记 1、可以在类前使用装饰器@pytest.mark.xxx; 2、也可以在类下使用pytestmark = [pytest.mark.标签1]; -
可以直接标记测试用例 在用例前使用装饰器@pytest.mark.xxx给用例打上标记
3.1.3、运行标记用例
调用 pytest.main()函数带上命令参数["-m 标签名"]即可,支持and与or关系运算;
- 运行单个标记如pytest.main(["-m 标签1"]);
- 运行多个标记如pytest.main(["-m 标签1 and 标签2"])或者pytest.main(["-m 标签1 or 标签2"]);
3.2、标记总结
- 一条用例可以有多个标记;
- 可以同时运行符合多个条件的标签,-m “标签1 and 标签2”;
- 取消警告信息,需要结合pytest.ini文件来使用;
- 可以使用pytest.mark来标记类、模块、方法;
- 可以以装饰器的方式标记,也可以使用关键变量pytestmark标记;
四、用例前置和后置
Pytest处理前置后置有两种方式可以处理: 1、第一种是通过setup和teardown这样的方法去处理; 2、第二种是通过fixture来实现的;
4.1 通过setup和teardown
1、模块级(setup_module/teardown_module),开始于模块始末,作用于全局(总用各执行一次); 用法:函数放在类外面才能生效; 2、函数级(setup_function/teardown_function),仅对函数用例生效(即不在类中,每个函数执行一次); 用法:函数放在类外面才能生效; 3、类级(setup_class/teardown_class),只在类中前后运行一次; 用法:函数放在类里面才能生效; 4、方法级(setup_method/teardown_method),开始于方法始末(在类中,每个方法执行一次); 用法:函数放在类里面才能生效; 5、类里面的(setup/teardown),运行在调用方法的前后(每个方法执行一次); 用法:函数放在类里面才能生效;
类级、方法级、类里面的前后置处理顺序:
A、setup_class是在所有用例执行前运行的,teardown_class是在所有用例执行完成后运行的。
B、setup_method是在每一条用例执行前,且在setup执行前运行的,teardown_method是在每一条用例执行完成后,且在teardown执行完成后运行的。
C、setup是在每条用例执行前运行,teardown是在每条用例执行完成后运行。
所以,他们的顺序是:
setup_class-
setup_method-setup-用例1-teardown-teardown_method-
setup_method-setup-用例2-teardown-teardown_method-
setup_method-setup-用例n-teardown-teardown_method-
……
teardown_class
4.2 通过fixture
pytest.fixture():作用于模块内的所有用例,但需要传递装饰函数为参数,可置于class内或class外; 1、fixture 可以作为一个函数的参数被调用 2、fixture可以在一个类、或者一个模块、或者整个session中被共享,加上范围scope参数即可,如@pytest.fixture(scope=‘module’)
- 会话级别: session,整个测试执行会话全部用例开始前执行/全部用例执行完后执行(是多个文件调用一次)
- 模块级别: module,只对模块级别生效,全部用例开始前执行/全部用例执行完后执行(每个.py文件调用一次)
- 类级别: class,只对类级别生效,整个测试类全部用例开始前执行/全部用例执行完后执行(每一个类调用一次)
- 函数级别: function,只对函数级别生效,每个用例开始前和结束后执行一次(每一个函数或方法都会调用)
3、fixture也可以单独存放 有的时候为了方便配置和访问,共享 fixture 函数,也可以将这样的fixture放到conftest.py文件中单独存放;conftest.py 文件中的 fixture 函数不需要在测试函数中导入,可以被 pytest 自动识别,查找顺序从测试类开始,然后是测试模块,然后是 conftest.py 文件,最后是内置插件和第三方插件。 4、同一个模块里出现多个范围的装饰 当出现多个范围装饰则优先实例化范围优先级高的,也就是优先级从大到小:session–>module–>class–>function; 6、yield方法实现setup/teardown的功能 在yield关键字之前的代码在case之前执行,yield之后的代码在case运行结束后执行
@pytest.fixture()
def login():
print(\"登录\")
yield
print(\"退出登录\")
7、addfinalizer方法实现setup/teardown的功能 addfinalizer也可以实现环境的清理,实现与yield方法相同的效果,跟yield不同的是需要注册作为终结器使用的函数
例如:
@pytest.fixture()
def login(request):
print(\"登录\")
def demo_finalizer():
print(\"退出登录\")
# 注册demo_finalizer为终结函数
request.addfinalizer(demo_finalizer)
注意request是固定的,不能随便写的
8、fixture中的参数 autouse 默认是False, autouse设置为True时,自动调用fixture功能。由于默认作用域为function,不指定scope则每个方法都会调用fixture方法。
9、fixture函数参数化 如果多条用例都需要调用相同参数,可以将fixture函数参数化。fixture 函数将执行每个参数值,fixture通过固定参数request传递。
#!/usr/bin/python
# -*- coding: UTF-8 -*-
import pytest
@pytest.fixture(scope="module", params=['测试1','测试2','测试3'])
def data(request):
yield request.param
class Test_Demo():
def test_case1(self, data):
print('测试用例01')
print(data)
if __name__ == "__main__":
pytest.main(["-sv", "test.py"])
运行结果如下:
test.py::Test_Demo::test_case1[\\u6d4b\\u8bd51] 测试用例01
测试1
PASSED
test.py::Test_Demo::test_case1[\\u6d4b\\u8bd52] 测试用例01
测试2
PASSED
test.py::Test_Demo::test_case1[\\u6d4b\\u8bd53] 测试用例01
测试3
PASSED
10、pytest.mark.usefixtures的使用 @pytest.mark.usefixtures的使用,usefixtures达到的效果和setup和teardown一样
11、 当case里需要传入多个 fixture 或者 yield 怎么办呢? 先后执行的顺序是什么? (“before”表示执行在case前,“yield”表示执行在case后)
- def test_1(before, yield) 与 def test_1(yield,before)
结论:不管你顺序如何,依旧会先执行case前的before,case结束后执行yield - def test_2(before1, before2, yield)
结论:这样有多个before,会依次按传参顺序先后执行。 - def test_3(before, yield1, yield2)
结论:这样有多个yield的,会依次从后往前执行,这里先执行yield2,再执行yield1。
五、pytest命令参数
- pytest test/: 执行test目录下所有文件
- pytest.main([’-s’,’-v’]): 写在代码中执行测试用例,默认执行当前目录及子目录下的所有文件,可以指定执行文件
- py.test -x : 首次失败后停止执行
- py.test --maxfail=2 : 两次失败之后停止执行
- py.test -k answer1 -v: 运行所有名字中含有的answer1的方法,-k 用来匹配名字中包含表达式的方法, -v 增加显示详细信息
- py.test -m : 运行通过marked的用例,-m 标记的名字
- @pytest.mark.xfail: Xfail标记的测试将会执行,但是不会被记入失败或成功中,如果失败了,也不会有任何追踪的信息
- @pytest.mark.skip: Skip 则意味着直接跳过,不会执行
六、生成HTML报告
- 首先安装插件:pip install pytest-html
- 运行命令:py.test -v -s --html=reportName.html
- 你将会在文件目录下生成reportName.html,用浏览器打开
- 上面生成的报告,css是独立的,分享报告的时候样式会丢失,为了更好的分享发邮件展示报告,可以把css样式合并到html里
pytest --html=report.html --self-contained-html - 在python中执行命令
pytest.main(['--html=./report.html', 'test_testing_topic.py'])
七、运行测试用例方式
1.、Pytest Marker 机制 可以在每一个测试用例加一个marker,比如pytest运行的时就只运行带有该marker的测试用例,比如下面的pytest.main(["-m test"]);
2、选择运行特定的某个测试用例 你可以按照某个测试用例的的模块,类或函数来选择你要运行的case,比如下面的方式就适合一开始在调试单个测试用例的时候; pytest -v test_pytest_markers.py::TestClass::test_method
3、选择运行特定的某个类 pytest -v test_pytest_markers.py::TestClass
4、用-k进行关键字匹配来运行测试用例名字子串 pytest -v -k http test_pytest_markers.py
5、重跑失败的用例,使用pytest-rerunfailures python3 -m pytest -reruns 重跑次数
6、多进程运行 安装pytest-xdist:pip install -U pytest-xdist 如何使用:使用参数"-n" py.test test_pyexample.py -n NUM 其中NUM填写并发的进程数。
八、断言
pytest里面的断言实际上就是python里面assert的断言方法,常用以下几种:
- assert xx 判断xx为真
- assert not xx 判断xx不为真
- assert a in b 判断b包含a
- assert a == b 判断a等于b
- assert a != b 判断a不等于b
九、数据驱动
pytest数据驱动,就是参数化,使用@pytest.mark.parametrize 在测试用例的前面加上:
- @pytest.mark.parametrize(“参数名”,列表数据)
参数名:用来接收每一项数据,并作为测试用例的参数。 列表数据:一组测试数据。 - @pytest.mark.parametrize(“参数1,参数2”,[(数据1,数据2),(数据1,数据2)])
十、常用的第三方库
参数值配置:
--workers=n 多进程运行需要加此参数,n是进程数。默认为1 【注:在windows系统中只能为1】
--tests-per-worker=n 多线程运行需要加此参数,n是线程数。
如果两个参数都配置了,就是进程并行,每个进程最多n个线程,总线程数:进程数*线程数
pytest-parallel的workers参数在windows系统下永远是1,在linux和mac下可以取不同值。
-
pytest-xdist 多进程并行与分布式执行,只支持多进程不支持多线程; 用法:使用参数"-n",如py.test test_pyexample.py -n NUM 注意:使用的前提是用例之间都是独立的,没有先后顺序,随机都能执行,可重复运行不影响其他用例 -
pytest-rerunfailures 支持用例失败重试功能 运行时指定:pytest -v -s 文件名.py -reruns 5 --reruns-delay 1 每次等1秒 重试5次 测试方法上指定:@pytest.mark.flaky(reruns=5,reruns-delay=1) 每次等1秒 重试5次 -
pytest-picked 插件可以实现只运行未提交到git仓库的代码
--picked=[{only,first}] Run the tests related to the changed files either on their own, or first
--mode=PICKED_MODE Options: unstaged, branch
使用示例:
pytest --picked 所有测试都将从已修改但尚未提交的文件和文件夹中运行
pytest --picked=first 首先运行修改后的测试文件中的测试,然后运行所有未修改的测试
pytest --picked --mode=branch 运行分支上已经被暂存但尚未提交的代码
pytest --picked --mode=unstaged # default 会默认执行所有的 Untracked 文件和 not staged 文件
-
pytest-ordering 指定用例的执行顺序 装饰器用法:@pytest.mark.run(order=xx) -
pytest-cov 在做单元测试时,代码覆盖率常常被拿来作为衡量测试好坏的指标,甚至,用代码覆盖率来考核测试任务完成情况; 运行用例的时候加上 --cov 参数 生成html的报告,pytest --cov --cov-report=html 指定被测代码,可以通过--cov=模块 来运行 -
pytest-instafail 可以在运行用例的时候,实时查看用例报错内容 用法: --instafail 方便实时查看报错内容,结合--tb=line参数,看起来更直观 -
pytest-tldr pytest-tldr 将默认输出限制为失败测试的回溯信息,并忽略了一些令人讨厌的颜色编码。添加 -v 标志会为喜欢它的人返回更详细的输出; -
pytest-django pytest-django 引入了使用 pytest fixture 测试 Django 项目的能力,而省略了导入 unittest 和复制/粘贴其他样板测试代码的需要,并且比标准的 Django 测试套件运行得更快; -
pytest-html pytest用于生成测试结果的HTML报告 用法:pytest --html=report.html --self-contained-html -
allure-pytest 生成Allure报告,Allure框架是一个灵活的轻量级多语言测试报告工具,它不仅以web的方式展示了简介的测试结果,而且允许参与开发过程的每个人从日常执行的测试中最大限度的提取有用信息;
用法:pytest.main(['-s', '-q', '--alluredir', './report/xml'])
-q 的意思是减少报告多余
--alluredir 的意思是生成allure报告的数据的目标目录,即测试目录运行后的结果,是生成xml的数据集合
生成Allure报告,在cmd下运行==> allure generate --clean ./report/xml/ -o ./results/html/
Allure学习参考:https://www.cnblogs.com/linuxchao/p/linuxchao-pytest-allure.html
-
pytest-assume 前面的断言失败了后继续执行 用法:pytest.assume(xxx == xxx) -
pytest-timeout 设置执行用例的超时时间 用法:pytest --timeout=2 -
pytest-repeat 重复执行,当执行一个测试用例时偶发性BUG时,我们可以使用 pytest-repeat 进行重复测试直到失败。 用法:pytest -v -s --count=2 test_open.py 重复执行2次 -
pytest-dependency 使用该插件可以标记一个test作为其他test的依赖,当依赖项执行失败时,那些依赖它的test将会被跳过; 用法:用 @pytest.mark.dependency()对所依赖的方法进行标记,使用@pytest.mark.dependency(depends=[“test_name”])引用依赖
import pytest
@pytest.mark.dependency()
def test_01(test):
assert False
@pytest.mark.dependency(depends=["test_01"])
def test_02(test):
print("执行测试2")
运行结果:test_01是test_02的依赖,故而test_01失败后,test_02被跳过
|