pytest是第三方库,使用前要先进行安装 pip install pytest
pytest单元测试框架的核心功能:
- 收集用例:进入目录,运行pytest
- 指定用例函数的定义规则:要以test开头的用例,支持普通函数,测试类
- 自动执行用例:如果不能识别,需要修改pycharm默认的执行器改成pytest
- 前置和后置夹具
- 断言:pytest直接用关键字assert 表达式
- 生成测试报告 :pytest --html=报告名称
1.编写测试用例
pytest用例编写规则很灵活:
- 直接定义函数,不需要测试类
- 编写测试类,不继承 unittest.TestCase
- 编写测试类,继承 unittest.TestCase,可以直接迁移(兼容)
前两种方法前面没有运行播放键,所以我们需要去修改pycharm默认的执行器
点击【File】-->【Settings】-->【Tools】-->【Python Integrated Tools】-->
找到【Default test runner】点击下拉框选择pytest
设置完成后再查看,三种方法前面都有运行播放键
2.执行测试用例
2.1命令执行测试用例
相比unittest来说,pytest不用编写特意收集用例的代码,进入相应目录执行命令即可
进入项目的目录执行运行用例时,收集的是整个项目下所有的用例,而进入指定的cases包下执行运行用例时,收集的用例只量这个cases包下的用例数量
2.2代码执行测试用例
run.py模块位置放在哪,执行的就是这个包下所有以test开头的模块的测试用例
修改run.py代码如下:(根目录下的run.py)
import pytest
# 收集并且运行用例
pytest.main()
运行结果:
修改run.py代码如下:(cases目录下的run.py)
运行结果:
3.断言
pytest中没有assertEqul,只有aseert,这个是经过pytest进行了封装后的关键字,相比unittest使用简单,不用写self
4.测试报告
4.1命令执行测试报告
pytest-html 第三方库,使用需要先进行安装 pip install pytest-html
直接在命令行内输入pytest --html=报告名称,可以自定义目录及报告名称,报告的样式没有unittest的美观,后面会介绍另一种报告的方法,相对样式要美观
4.2代码执行测试报告
import pytest
from datetime import datetime
# 获取时间戳
ts = datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
# 拼接文件目录
filename = f'reports/report-{ts}.html'
# 收集并且运行用例
pytest.main([f'--html={filename}'])
运行结果:
5.前置和后置夹具
pytest 和 unittest是可以兼容的,所以之前使用unittest编写的代码不用修改,直接使用pytest命令直接运行,如果没继承的话如何使用呢,新建test_pytest_fixture.py代码如下:
import pytest
# 声明这是一个夹具,这个夹具就是个函数
@pytest.fixture()
def fixt():
# 这里相当于setup
print('正在执行前置条件')
# yield之前就是前置,yield之后就是后置
yield
# 这里相当于tearDown
print('正在执行后置清理')
class TestFixture:
def test_fixture(self, fixt):
assert 1 + 1 == 2
class TestFixture1:
def test_fixture1(self, fixt):
assert 1 + 1 == 2
运行结果
夹具的封装:
新建fixtures.py,代码如下
import pytest
# 声明这是一个夹具,这个夹具就是个函数
@pytest.fixture()
def fixt():
# 这里相当于setup
print('正在执行前置条件')
# yield之前就是前置,yield之后就是后置
yield
# 这里相当于tearDown
print('正在执行后置清理')
修改test_pytest_fixture.py代码如下:
from common.fixtures import fixt
class TestFixture:
def test_fixture(self, fixt):
assert 1 + 1 == 2
class TestFixture1:
def test_fixture1(self, fixt):
assert 1 + 1 == 2
运行结果:
共享fixture:
- 将所有的夹具全部放到一个固定的模块文件,conftest.py
- 所有导入夹具的操作就可以省略了,pytest运行时会自动在conftest.py中查找
修改fixtures.py为conftest.py,代码不用修改,再次运行test_pytest_fixture.py结果如下图
注意:conftest.py要跟测试用例存放在同一个目录下,否则会报错
6.fixture作用域
pytest有非常灵活的作用域管理,有以下几个级别:
- function 每个函数/方法运行一次
- class 每个类运行一次
- module 每个模块运行一次
- package 每个包运行一次
- session 每个传话运行一次
项目中通常使用function和class级别多一些,其他的了解一下即可
修改conftest.py代码如下:
import pytest
# 声明这是一个夹具,这个夹具就是个函数
@pytest.fixture()
def fixt():
# 这里相当于setup
print('正在执行前置条件')
# yield之前就是前置,yield之后就是后置
yield
# 这里相当于tearDown
print('正在执行后置清理')
@pytest.fixture(scope='function')
def function_fixt():
print('function')
yield
print('function finished')
@pytest.fixture(scope='class')
def class_fixt():
print('class')
yield
print('class finished')
@pytest.fixture(scope='module')
def module_fixt():
print('module')
yield
print('module finished')
注意:function级别,可以默认不写,上面的fixt和fnuction_fixt一个没有一个写了,但都表示是function级别
修改test_pytest_fixture.py代码如下:
class TestFixture:
def test_fixture(self, function_fixt,class_fixt,module_fixt):
assert 1 + 1 == 2
def test_fixture1(self, function_fixt,class_fixt,module_fixt):
assert 1 + 1 == 2
class TestFixture2:
def test_fixture21(self, function_fixt,class_fixt,module_fixt):
assert 1 + 1 == 2
def test_fixture22(self, function_fixt,class_fixt,module_fixt):
assert 1 + 1 == 2
运行结果:
pytest夹具还提供了一个参数,autouse:自动使用夹具
修改conftest.py代码如下:
import pytest
# 声明这是一个夹具,这个夹具就是个函数
@pytest.fixture()
def fixt():
# 这里相当于setup
print('正在执行前置条件')
# yield之前就是前置,yield之后就是后置
yield
# 这里相当于tearDown
print('正在执行后置清理')
@pytest.fixture(scope='function', autouse=True)
def function_fixt():
print('function')
yield
print('function finished')
@pytest.fixture(scope='class', autouse=True)
def class_fixt():
print('class')
yield
print('class finished')
@pytest.fixture(scope='module', autouse=True)
def module_fixt():
print('module')
yield
print('module finished')
修改test_pytest_fixture.py代码如下:
class TestFixture:
def test_fixture(self):
assert 1 + 1 == 2
def test_fixture1(self):
assert 1 + 1 == 2
class TestFixture2:
def test_fixture21(self):
assert 1 + 1 == 2
def test_fixture22(self):
assert 1 + 1 == 2
运行结果:
总结:
- autouse不要随便使用,以免夹具搞混乱
- 使用了autouse后,方法/函数中就不用填写调用夹具的参数了
7.参数化
相当于unittest中的ddt
import pytest
data = [1, 2, 3]
class TestParams:
@pytest.mark.parametrize('info', data)
def test_params(self, info):
print(info)
assert 1 + 1 == 2
运行结果:
注意:在pytest中使用pamaretrize或fixture时,就不能使用unittest,因为参数化跟夹具是不兼容unittest的
实践当中会有两种模式:
模式1:
- 使用unittest编写测试用例,使用ddt和夹具setup
- 使用pytest运行
模式2:
全部使用pytest,完全舍弃unittest
总结:unittest是内置库,跟python版本走,稳定性高,而pytest是第三方库,环境要求高,版本不匹配的话容易出问题,所以使用模式1是很常见的
8.用例筛选
筛选步骤:
步骤0:提前把标记名注册到pytest中(这步可以选择不做)
步骤1:在测试类或方法上做一个标记/标签
步骤2:在运行用例时,通过标记执行
import pytest
class TestMark:
@pytest.mark.success
def test_mark(self):
assert 1 + 1 == 2
def test_mark1(self):
assert 1 + 1 == 2
运行结果:
如果觉得麻烦还可以在整个类上声明一下
import pytest
@pytest.mark.success
class TestMark:
def test_mark(self):
assert 1 + 1 == 2
def test_mark1(self):
assert 1 + 1 == 2
运行结果:
使用run.py运行筛选用例:
import datetime
import pytest
# 获取当前时间戳
ts = datetime.datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
# 路径拼接
filename = f'reports/report-{ts}.html'
# 收集并执行用例(只运行筛选的用例)
pytest.main(['-m', 'success', f'--html={filename}'])
运行结果:
注意:
- 标记名可以随便命名
- 在参数化中不可以使用
- 在运行结果中我们可以看到有警告信息,那是因为没有在pytest中注册标记名
注册标记名:
点击运行结果中带mark.html的链接,跳转到Marking test functions with attributes — pytest documentation网站
这里提供了两种方法注册标记名,直接复制代码设置即可
新建一个pytest.ini文件,编写代码后,运行
新建一个pyproject.toml文件,编写代码后,运行
注册标记后,我们从运行结果可以看出,不显示警告信息了
mark支持逻辑运算and,or,not
进入相应目录下,执行pytest -m "success and login"
进入相应目录下,执行pytest -m "success or login"
进入相应目录下,执行>pytest -m "success and not login"