1.简介
Pytest是基于python语言的单元测试框架,也是一个命令行工具,具有以下特点:
- 入门简单,易上手
- 支持大量的第三方插件,如:失败重试,控制用例执行顺序等
- 基于配置文件可以简单的集成CI(持续集成)工具中
2.快速入门
安装
pip install pytest
基本格式
def add(x, y):
return x+y
class TestAddFunc(object): # 测试用例的类名必须以 Test 为开头
def test_01(self): # 方法名和函数名必须以 test 为开头
print(add(10,20))
def test_02(self):
print(add(10, 20))
def test_03(self):
print(add(10, 20))
测试运行
pytest中提供了三种方式给测试 人员运行测试用例
- 命令行运行
pytest -s -v 文件名
# -s:输出测试用例中的print打印的信息
# -v: 输出测试用例的类名和方法名
# -x: 一旦发现测试用例失败,立即停止运行
# no:warnings: 不显示警告
- Pycharm运行
运行测试测试文件即可 - mian函数运行
# pytest.main(["模块文件名::类名::方法名字","参数"])
pytest.main(["./demo/pytest_01_基本格式","-sv"])
# pytest.main(["./demo/pytest_01_基本格式::TestAddFunc","-sv"])
# pytest.main(["./demo/pytest_01_基本格式::TestAddFunc::test_01","-sv"])
测试脚手架
- 方法级别 :setup 和 teardown ----> 每个用例都会执行
- 类级别: setup_class 和 teardown_class ----> 每个测试类都会执行
- 模块级别: setup_module 和 teardown_module ----> 每个测试模块都会执行
import pytest
def add(x, y):
return x + y
def setup_module():
print("模块初始化")
def teardown_module():
print("模块结束")
class TestAddFunc(object): # 测试用例的类名必须以 Test 为开头
def setup_class(self):
print("类初始化")
def teardown_class(self):
print("类结束")
def setup(self):
print("用例初始化")
def teardown(self):
print("用例结束")
def test_01(self): # 方法名和函数名必须以 test 为开头
print(add(10, 20))
def test_02(self):
print(add(10, 20))
def test_03(self):
print(add(10, 20))
if __name__ == '__main__':
# pytest.main(["模块文件名::类名::方法名字","参数"])
pytest.main(["./demo/pytest_01_基本格式", "-sv"])
# pytest.main(["./demo/pytest_01_基本格式::TestAddFunc","-sv"])
# pytest.main(["./demo/pytest_01_基本格式::TestAddFunc::test_01","-sv"])
以上的执行顺序:
基于配置文件运行pytest
在pytest提供的终端运行测试用例的方法上,pytest也支持使用配置文件来指定运行的参数,常用的配置文件名为
- pytest.ini
- tox.ini
- setup.cfg
配置文件一般保存在项目的根目录下
pytest.ini
[pytest]
# 指定运行参数
addopts = -s -v
# 搜索测试文件的目录路径
testpaths = ./
# 搜索测试文件名格式
python_files = test_*.py
# 搜索测试类格式
python_classes = Test*
# 搜索测试方法名格式
python_functions = test_*
运行用例
pytest
断言
pytest中的断言就是python中的关键字assert
格式
assert 表达式, 断言错误提示信息
import pytest
def add(x, y):
return x+y
class TestAddFunc(object): # 测试用例的类名必须以 Test 为开头
def test_01(self): # 方法名和函数名必须以 test 为开头
res = add(10,20)
assert res == 30,"两者不相等"
def test_02(self):
res = add(10, 20)
assert type(res) is str, "数据类型不是字符串"
跳过
根据特定的条件,不执行标识的测试函数
@pytest.mark.skipif(判断条件, reson="跳过原因")
import pytest
version = (2,1,2)
def add(x, y):
return x+y
class TestAddFunc(object):
def test_01(self):
res = add(10,20)
assert res == 30,"两者不相等"
@pytest.mark.skipif(version>(2,1,1),reason="版本太低,不测试")
def test_02(self):
res = add(10, 20)
assert type(res) is str, "数据类型不是字符串"
提示:该跳过装饰器可以添加在函数上也可以添加在类上面
参数化
参数化可以将多个测试用例简化为一个或者几个测试函数,说白了就是多个测试用例具有相同的测试参数
import pytest
version = (2,1,2)
def add(x, y):
return x+y
class TestAddFunc(object):
@pytest.mark.parametrize("args",[{'x':10, "y":20,}, {'x':'10', "y":'20',}, {'x':20, "y":20,}])
def test_start(self, args):
res = add(**args)
print(res)
assert res == 30,"两者不相等"
3.进阶使用
fixture
fixture有个scope的参数,可以控制fixture的作用范围(从大到小):session>mudule>class>function
session:多个文件调用一次,可以跨.py文件调用
mudule: 每一个.py调用一次
class: 每个类调用一次
function:每一个方法或者函数调用一次
- 实现参数化效果
简单使用
import pytest
@pytest.fixture(scope="class")
def fixture_data():
print("运行fixture")
return 10, 20
def add(x, y):
return x+y
class TestAddFunc(object):
def test_01(self, fixture_data): # 参数名字就是脚手架的名字,必须保持一致
res = add(fixture_data[0],fixture_data[1])
assert res == 30,"两者不相等"
def test_02(self, fixture_data):
res = add(fixture_data[0],fixture_data[1])
assert res == 30,"两者不相等"
多个脚手架使用
import pytest
@pytest.fixture(scope="class")
def fixture_data_01():
print("运行fixture-01")
return 10, 20
@pytest.fixture(scope="class")
def fixture_data_02():
print("运行fixture-02")
return 10, 30
def add(x, y):
return x+y
# 可以显示的指明调用的fixture,不加也是可以的,主要是便于阅读代码
@pytest.mark.usefixtures("fixture_data_01")
@pytest.mark.usefixtures("fixture_data_02")
class TestAddFunc(object):
def test_01(self, fixture_data_01):
res = add(fixture_data_01[0],fixture_data_01[1])
assert res == 30,"两者不相等"
def test_02(self, fixture_data_02):
res = add(fixture_data_02[0],fixture_data_02[1])
assert res == 40,"两者不相等"
- 自动执行
可以实现类似setup和teardown的作用,在执行用例之前或者之后运行
import pytest
# 在每一个测试用例执行之前执行
@pytest.fixture(scope="function", autouse=True)
def fixture_data_01():
print("运行fixture-01")
return 10, 20
# 不会自动执行
@pytest.fixture(scope="function")
def fixture_data_02():
print("运行fixture-02")
return 10, 30
def add(x, y):
return x+y
class TestAddFunc(object):
def test_01(self):
pass
def test_02(self):
pass
- yied使用
在上面的案例中我们可以实现类似setup的作用,即可以在用例执行之前运行,那么如何实现teardown的作用呢?就可以使用yield的关键字
说的更直白一点就是yied关键字可以实现teardown和setup的集合,同时yied的返回值可以充当用例的执行入参,非常类似python中的上下文操作
import pytest
@pytest.fixture(scope="function", autouse=True)
def fixture_data_01():
print("开始运行fixture-01")
yield 10
print("结束运行fixture-01")
def add(x, y):
return x+y
class TestAddFunc(object):
def test_01(self, fixture_data_01):
print(fixture_data_01)
pass
def test_02(self):
pass
- fixture代码分离
我们可以将fixture的代码写好,放在一个名为conftest.py文件中,可以被pytest自动识别,进而实现了测试用例和fixture的分离
conftest.py应该和测试代码放在同一个目录下,在实际开发中可以有多个conftest.py文件,每个文件的作用域是当前自身所在的当前目录及其子目录
conftest.py 代码
import pytest
@pytest.fixture(scope="function", autouse=True)
def fixture_data_01():
print("开始运行fixture-01")
yield 10
print("结束运行fixture-01")
测试用例代码
def add(x, y):
return x+y
class TestAddFunc(object):
def test_01(self, fixture_data_01):
print(fixture_data_01)
pass
def test_02(self):
pass
第三方组件的使用
- 控制用例执行顺序
pytest默认执行用例的顺序是按照源代码的上下顺序执行的,如果希望控制执行顺序,可以通过第三方组件pytest-ordering实现
安装
pip install pytest-ordering
使用
执行顺序为:优先执行正序排序的方法,在执行没有排序的方法,最后执行负数排序的方法,如果多个方法都是正序,则先执行排序小,负数亦然
import pytest
class TestAdd(object):
@pytest.mark.run(order=-1)
def test_01(self):
print("test_01")
@pytest.mark.run(order=-2)
def test_02(self):
print("test_02")
def test_03(self):
print("test_03")
@pytest.mark.run(order=1)
def test_04(self):
print("test_04")
@pytest.mark.run(order=2)
def test_05(self):
print("test_05")
注意:pytest-ordering组件不能和fixture一起使用,会报错的,如果在使用pytest_ordering的情况下还需要参数化,可以使用@pytest.mark.parameterze
- 失败用例重试
针对网络场景和服务端性能不稳定的情况下,进行测试用常常发生用例运行失败的情况,我们可以指定重试次数,已达到重试更加准确的结果
安装
pip install pytest-rerunfailures
使用
在执行pytest中添加执行参数即可
--reruns n :重试次数
--reruns-delay m:(重试间隔)
全局失败用例重试
配置文件pytest.ini
[pytest]
# 指定运行参数
addopts = -s -v -p no:warnings --reruns 3 --reruns-delay 2
# 搜索测试文件的目录路径
testpaths = ./
# 搜索测试文件名格式
python_files = pytest_*.py
# 搜索测试类格式
python_classes = Test*
# 搜索测试方法名格式
python_functions = test_*
不通过的用例会最多执行3次,每次重试间隔2秒,最后返回结果
局部失败用例测试
import pytest
def add(x, y):
return x+y
class TestAddFunc(object):
def test_01(self, fixture_data_01):
print(fixture_data_01)
pass
@pytest.mark.flaky(reruns=4, reruns_delay=2)
def test_02(self):
assert 1== 2
注意:
局部参数会覆盖全局参数,也就是说使用了局部参数,全局用法就会失效
与pytest.fixture脚手架也会存在冲突,不能混合使用
- 用例并发运行
当测试用例非常多的时候,一条条执行是很浪费时间的。如果测试用例之间彼此独立,没有任何依赖关系,就可以并发运行用例,从而节省时间
pytest-xdist可以实现并发运行,属于进程级别的
安装
pip install pytest-xdist
使用
在运行测试用例的时候添加一个参数即可,可以指定运行测试的时候所开启的进程数量
-n 4 :使用4个进程运行,
-n auto : 自动检测CPU核数,根据CPU核数创建对应数量的进程个数
pytest.ini代码
[pytest]
# 指定运行参数
addopts = -s -v -p no:warnings --reruns 3 --reruns-delay 2 -n 4
# 搜索测试文件的目录路径
testpaths = ./
# 搜索测试文件名格式
python_files = pytest_*.py
# 搜索测试类格式
python_classes = Test*
# 搜索测试方法名格式
python_functions = test_*
测试用例
import pytest
class TestAdd(object):
@pytest.mark.run(order=-1)
def test_01(self):
print("test_01")
@pytest.mark.run(order=-2)
def test_02(self):
print("test_02")
assert 1 ==1
def test_03(self):
print("test_03")
@pytest.mark.run(order=1)
def test_04(self):
print("test_04")
@pytest.mark.run(order=2)
def test_05(self):
print("test_05")
运行结果
- 生成HTML格式测试用例
生成的测试报告很简单,在实际的开发中,还是使用Allure来生成测试报告
安装
pip install pytest-html
使用
在执行测试用例的时候,添加参数 --html=生成测试报告路径 即可
pytest.ini文件
[pytest]
# 指定运行参数
addopts = -s -v -p no:warnings --reruns 3 --reruns-delay 2 -n 4 --html=report.html
# 搜索测试文件的目录路径
testpaths = ./
# 搜索测试文件名格式
python_files = pytest_*.py
# 搜索测试类格式
python_classes = Test*
# 搜索测试方法名格式
python_functions = test_*