Pytest的学习
- 当前使用python3.x版本
- pytest是python的一种单元测试框架,同自带的Unittest测试框架类似,相比于Unittest框架使用起来更简洁,效率更高
- 特点:
➢ 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考
➢ 支持简单的单元测试和复杂的功能测试
➢ 支持参数化
➢ 执行测试过程中可以将某些测试跳过或对某些预期失败的Case标记成失败
➢ 支持重复执行失败的Case
➢ 支持运行由Nose , Unittest编写的测试Case
➢ 具有很多第三方插件,并且可以自定义扩展
➢ 方便的和持续集成工具集成
import pytest
class Test_pytest:
# 第一种传参:return返回值
@pytest.fixture()
def data(self):
return 1
def test_a(self, data):
assert data == 1
@pytest.mark.usefixtures("data") # 拿不到参数
def test_a_1(self):
assert False
# 第二种传参:params参数值
@pytest.fixture(params=[1, 2, 3])
def more_data(self, request):
return request.param
def test_b(self, more_data):
assert more_data != 2
# 第三种传参:parametrize
@pytest.mark.parametrize("a,b", [(1, 2), (0, 3)])
def test_c(self, a, b):
assert a+b == 1
1.1 安装Pytest
1.1.1 安装包方式
- 进入下载解压后的安装包的路径中
sudo python setup install
或python setup install
注意:
- 以管理员身份运行该命令
- 查看版本
pytest --version
1.1.2 命令行方式
sudo pip3 install -U pytest
或pip3 install -U pytest
注意:
- 以管理员身份运行该命令
- 查看版本
pytest --version
1.1.3 Pytest运行方式
1. 测试类主函数模式
pytest.main()
2. 命令行模式
pytest -s xxx/xxx.py
1.1.4 示例代码测试
vim test_abc.py
import pytest # 引入pytest包
def test_a(): # test开头的测试函数
print("------->test_a")
assert 1 # 断言成功
def test_b():
print("------->test_b")
assert 0 # 断言失败
if __name__ == '__main__':
pytest.main("-s test_abc.py") # 调用pytest的main函数执行测试
执行结果:
test_abc.py
------->test_a
. # .(代表成功)
------->test_b
F # F(代表失败)
1.2 Pytest使用
1.2.1 Pytest的setup和teardown函数
- setup和teardown主要分为:模块级、类级、功能级、函数级
- 存在于测试类内部
1. 函数级别
setup()
和teardown()
- 在一个类的内部
- 运行于测试方法的始末,即:运行一次测试函数会运行一次setup和teardown
import pytest
class Test_ABC:
# 函数级开始
def setup(self):
print("------->setup_method")
# 函数级结束
def teardown(self):
print("------->teardown_method")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->setup_method # 第一次 setup()
------->test_a
.
------->teardown_method # 第一次 teardown()
------->setup_method # 第二次 setup()
------->test_b
.
------->teardown_method # 第二次 teardown()
2. 类级别
setup_class
和teardown_class
- 在一个类的内部
- 运行于测试类的始末,即:在一个测试内只运行一次,不关心测试类内有多少个测试函数
import pytest
class Test_ABC:
# 测试类级开始
def setup_class(self):
print("------->setup_class")
# 测试类级结束
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->setup_class # 第一次 setup_class()
------->test_a
.
------->test_b
F
------->teardown_class # 第一次 teardown_class()
1.2.2 Pytest配置文件
pytest的配置文件通常放在测试目录下,名称为pytest.ini,命令行运行时会使用该配置文件中的配置
GBK编码
- 配置pytest命令行运行参数
空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数
[pytest]
addopts = -s ...
- 配置测试搜索的路径
当前目录下的scripts文件夹 -可自定义
[pytest]
testpaths = ./scripts
- 配置测试搜索的文件名
当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件 -可自定义
[pytest]
python_files = test_*.py
- 配置测试搜索的测试类名
当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类 -可自定义
[pytest]
python_classes = Test_*
- 配置测试搜索的测试函数名
当前目录下的scripts文件夹下,以test_开头,以.py结尾的所有文件中,以Test_开头的类内,以test_开头的方法 -可自定义
[pytest]
python_functions = test_*
- default
[pytest]
# 命令行参数
addopts = -s --reruns 1 --html=./report/test_report.html
# 测试路径
testpaths = ./scripts
# 搜索文件名
python_files = test001.py
# 搜索测试类名
python_classes = Test_*
# 搜索测试方法名
python_functions = test_*
1.2.3 Pytest常用插件
- 插件网址:https://plugincompat.herokuapp.com
- 前置条件:
文件路径:Test_App/{test_abc.py,pytest.ini}
pyetst.ini配置文件内容:有中文就用GBK编码
[pytest]
# 命令行参数
addopts = -s
# 搜索文件名
python_files = test_*.py
# 搜索的类名
python_classes = Test_*
# 搜索的函数名
python_functions = test_*
1. Pytest测试报告
通过命令行方式,生成xml/html格式的测试报告,存储于用户指定路径
插件名称:pytest-html
- 安装方式:
- 安装包方式
python setup.py install
- 命令行
pip3 install pytest-html
或pip3 install -U pytest-html
- 使用方法:
格式:pytest --html=用户路径/report.html
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 0 # 断言失败
提示:运行方式
- 修改Test_App/pytest.ini文件,添加报告参数
addopts = -s --html=./report.html
参数 说明 -s 输出程序运行信息 --html=./report.html
在当前目录下生成report.html文件 若要生成xml文件,可将
--html=./report.html
改成--html=./report.xml
- 命令行运行
cd Test_App
pytest
- 执行结果
在当前目录会生成assets
文件夹和report.html
文件
2. Pytest控制函数执行顺序
函数修饰符的方式标记被测试函数执行的顺序
插件名称:pytest-ordering
- 安装方式:
- 安装包方式
python setup.py install
- 命令行
pip3 install pytest-ordering
或pip3 install -U pytest-ordering
- 使用方法:
- 标记于被测试函数
格式:@pytest.mark.run(order=x)
- 根据order传入的参数来解决运行顺序
- order值全为正数或全为负数时,运行顺序:值越小,优先级越高
- 正数和负数同时存在:正数优先级高
- order值为负数时,优先级低于没有被标记的测试方法
- order值为正数时,优先级高于没有被标记的测试方法
默认情况下,pytest是根据测试方法名由小到大执行的,可以通过第三方插件包改变其运行顺序
eg:
第一种:默认执行方式
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 0
if __name__ == '__main__':
pytest.main("-s test_abc.py")
结果:
test_abc.py
------->setup_class
------->test_a # 默认第一个运行
.
------->test_b # 默认第二个运行
F
------->teardown_class
第二种:
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.run(order=2)
def test_a(self):
print("------->test_a")
assert 1
@pytest.mark.run(order=1)
def test_b(self):
print("------->test_b")
assert 0
if __name__ == '__main__':
pytest.main("-s test_abc.py")
结果:
test_abc.py
------->setup_class
------->test_b # order=1 优先运行
F
------->test_a # order=2 晚于 order=1 运行
.
------->teardown_class
3. Pytest失败重试
通过命令行方式,控制失败函数的重试次数
插件名称:pytest-rerunfailures
- 安装方式:
1.安装包方式python setup.py install
2.命令行pip3 install pytest-rerunfailures
或pip3 install -U pytest-rerunfailures
- 使用方法:
命令行格式:n为重试的次数pytest --reruns n
eg:
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 0 # 断言失败
- 运行方式:
1.修改Test_App/pytest.ini文件,添加失败重试参数addopts = -s --reruns 2 --html=./report.html
-s 输出程序运行信息
--reruns 2 失败测试函数重试两次
--html=./report.html 在当前目录下生成report.html文件
2.命令行进入Test_App目录
3.执行命令:pytest
- 执行结果:
在测试报告中可以看到两次重试记录
4. pytest之fixture
fixture修饰器来标记固定的工厂函数,在其他函数、模块、类或整个工程调用它时,会被激活并优先执行,通常会被用于完成预置处理和重复操作
- 用在
conftest.py
文件中,被fixure标记的函数可作为全局变量 - 方法:
fixture(scope="function", params=None, autouse=False, ids=None, name=None)
常用参数 | 说明 |
---|---|
scope | 被标记方法的作用域 |
function | default,作用于每个测试方法,每个test都运行一次 |
class | 作用于整个类,每个class的所有test只运行一次 |
module | 作用于整个模块,每个module的所有test只运行一次 |
session | 作用于整个session(慎用),每个session只运行一次 |
params | (list类型)提供参数数据,供调用标记方法的函数使用 |
autouse | 是否自动运行,默认为False不运行,设置为True自动运行 |
eg:
- 通过参数引用
@pytest.fixture()
import pytest
class Test_ABC:
@pytest.fixture()
def before(self):
print("------->before")
def test_a(self,before): # ⚠️ test_a方法传入了被fixture标识的函数,已变量的形式
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现before会优先于测试函数运行
------->test_a
.
- 通过函数引用
@pytest.fixture()
@pytest.mark.usefixtures("before")
import pytest
@pytest.fixture() # fixture标记的函数可以应用于测试类外部
def before():
print("------->before")
@pytest.mark.usefixtures("before")
class Test_ABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现before会优先于测试类运行
------->setup
------->test_a
.
- 默认设置为运行
@pytest.fixture(autouse=True) # 设置为默认运行
import pytest
@pytest.fixture(autouse=True) # 设置为默认运行
def before():
print("------->before")
class Test_ABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现before自动优先于测试类运行
------->setup
------->test_a
.
- 设置作用域为function
@pytest.fixture(scope='function',autouse=True) # 作用域设置为function,自动运行
import pytest
@pytest.fixture(scope='function',autouse=True) # 作用域设置为function,自动运行
def before():
print("------->before")
class Test_ABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 运行第一次
------->setup
------->test_a
.------->before # 运行第二次
------->setup
------->test_b
.
- 设置作用域为class
@pytest.fixture(scope='class',autouse=True) # 作用域设置为class,自动运行
import pytest
@pytest.fixture(scope='class',autouse=True) # 作用域设置为class,自动运行
def before():
print("------->before")
class Test_ABC:
def setup(self):
print("------->setup")
def test_a(self):
print("------->test_a")
assert 1
def test_b(self):
print("------->test_b")
assert 1
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->before # 发现只运行一次
------->setup
------->test_a
.
------->setup
------->test_b
.
- 返回值
第一种:@pytest.fixture()
import pytest
@pytest.fixture()
def need_data():
return 2 # 返回数字2
class Test_ABC:
def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 拿到返回值做一次断言
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
test_abc.py
------->test_a
.
第二种:@pytest.fixture(params=[1, 2, 3])
request
request.param
- 运行三次
import pytest
@pytest.fixture(params=[1, 2, 3])
def need_data(request): # 传入参数request系统封装参数
return request.param # 取列表中单个值,默认的取值方式
class Test_ABC:
def test_a(self,need_data):
print("------->test_a")
assert need_data != 3 # 断言need_data不等于3
if __name__ == '__main__':
pytest.main("-s test_abc.py")
执行结果:
# 可以发现结果运行了三次
test_abc.py
1
------->test_a
.
2
------->test_a
.
3
------->test_a
F
5. 跳过测试函数
根据特定的条件,不执行标识的测试函数
- 格式:
skipif(condition, reason=None)
参数 | 说明 |
---|---|
condition | 跳过的条件,必传参数 |
reason | 标注原因,必传参数 |
- 使用方法:
@pytest.mark.skipif(condition, reason="xxx")
eg:
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
@pytest.mark.skipif(condition=2>1,reason = "跳过该函数") # 跳过测试函数test_b
def test_b(self):
print("------->test_b")
assert 0
执行结果:
test_abc.py
------->setup_class
------->test_a #只执行了函数test_a
.
------->teardown_class
s # 跳过函数
6. 标记为预期失败函数
标记测试函数为失败函数
- 格式:
xfail(condition=None, reason=None, raises=None, run=True, strict=False)
常用参数 | 名称 |
---|---|
condition | 预期失败的条件,必传参数 |
reason | 失败的原因,必传参数 |
- 使用方法:
@pytest.mark.xfail(condition, reason="xx")
eg:
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
def test_a(self):
print("------->test_a")
assert 1
@pytest.mark.xfail(2 > 1, reason="标注为预期失败") # 标记为预期失败函数test_b
def test_b(self):
print("------->test_b")
assert 0
执行结果:
test_abc.py
------->setup_class
------->test_a
.
------->test_b
------->teardown_class
x # 失败标记
7. 函数数据参数化
方便测试函数对测试属于的获取
- 格式:
parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
常用参数 | 说明 |
---|---|
argnames | 参数名 |
argvalues | 参数对应值,类型必须为list |
当参数是一个值时,格式为[value]
当参数个数大于一个时,格式为[(param_value1,param_value2.....),(param_value1,param_value2.....)]
- 使用方法:
@pytest.mark.parametrize(argnames,argvalues)
⚠️ 参数值为N个,测试方法就会运行N次
eg:
- 单个参数示例
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a",[3,6]) # a参数被赋予两个值,函数会运行两遍
def test_a(self,a): # 参数必须和parametrize里面的参数一致
print("test data:a=%d"%a)
assert a%3 == 0
执行结果:
test_abc.py
------->setup_class
test data:a=3 # 运行第一次取值a=3
.
test data:a=6 # 运行第二次取值a=6
.
------->teardown_class
- 多个参数示例
import pytest
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a,b",[(1,2),(0,3)]) # 参数a,b均被赋予两个值,函数会运行两遍
def test_a(self,a,b): # 参数必须和parametrize里面的参数一致
print("test data:a=%d,b=%d"%(a,b))
assert a+b == 3
执行结果:
test_abc.py
------->setup_class
test data:a=1,b=2 # 运行第一次取值 a=1,b=2
.
test data:a=0,b=3 # 运行第二次取值 a=0,b=3
.
------->teardown_class
- 函数返回值类型示例
import pytest
def return_test_data():
return [(1,2),(0,3)]
class Test_ABC:
def setup_class(self):
print("------->setup_class")
def teardown_class(self):
print("------->teardown_class")
@pytest.mark.parametrize("a,b",return_test_data()) # 使用函数返回值的形式传入参数值
def test_a(self,a,b):
print("test data:a=%d,b=%d"%(a,b))
assert a+b == 3
执行结果:
test_abc.py
------->setup_class
test data:a=1,b=2 # 运行第一次取值 a=1,b=2
.
test data:a=0,b=3 # 运行第二次取值 a=0,b=3
.
------->teardown_class
更新中......