赞
踩
pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:
####(1)主函数运行
fixture属于pytest中的一个方法。fixture可以用作测试用例的前置和后置操作,其中fixture命令规范没有像setup和teardown固定格式。可以随意命名。控制fixture的前置和后置操作是通过yield关键字进行来区分的,代码在yield前面的属于前置操作,代码在yield后面的属于后置操作。并且fixture也没有强烈的要求必须要前后置同时存在,可以只存在前置也可以只存在后置。fixture如果有后置内容,无论遇到什么问题,都会进行执行后置的代码。
注意:在conftest.py文件中的多个fixture函数还可以互相调用,但是要注意的是,比如fixture1调用fixture2时,那么fixture1的scope范围一定要比fixture2的范围小,否则就会报错
# 定义一个夹具函数,使用装饰器pytest.fixture @pytest.fixture() def login(): print("用户登录") yield print("用户登出") # 方式1:作为参数传递 def test_demo_01(login): print("执行 test_demo_01 用例") # 方式2:使用 pytest.mark.usefixtures 装饰器使用指定fixture,可以传入多个 fixture,执行顺序依次执行 @pytest.mark.usefixtures("login") def test_demo_02(): print("执行 test_demo_02 用例")
语法:@pytest.fixture(scope=“function”, params=None, autouse=False, ids=None, name=None)
参数解释:
取值 | 作用 |
---|---|
function | 函数级 每一个函数或方法都会调用 |
class | 类级 每一个类都会被调用 |
module | 模块级 每一个.py文件调用一次 |
session | 会话级 次会话只需要运行一次,会话内所有方法及类,模块都共享这个方法 |
@pytest.fixture(autouse=True)
def fix_demo():
print("我是 fix_demo 的返回值")
# 不需要传入fixture函数
def test_demo_03():
print("执行 test_demo_03 用例")
执行结果:
test_demo_file_01.py::test_demo_03 我是 fix_demo 的返回值
执行 test_demo_03 用例
PASSED
@pytest.fixture(name="new_fix_demo") def fix_demo(): print("我是 fix_demo 的返回值") # 传入fixture函数名 def test_demo_03(fix_demo): print("执行 test_demo_03 用例") # 传入重命名的fixture名称 def test_demo_04(new_fix_demo): print("执行 test_demo_04 用例") #执行后 test_demo_03 用例将报错:fixture 'fix_demo' not found
@pytest.fixture(params=["张三", "李四", "王五"]) def fix_demo(request): temp = request.param print("我是{0},我来签到".format(temp)) return temp def test_user(fix_demo): print("{0}已签到".format(fix_demo)) 运行结果: test_demo_file_01.py::test_user[\u5f20\u4e09] 我是张三,我来签到 张三已签到 PASSED test_demo_file_01.py::test_user[\u674e\u56db] 我是李四,我来签到 李四已签到 PASSED test_demo_file_01.py::test_user[\u738b\u4e94] 我是王五,我来签到 王五已签到 PASSED # 或者与pytest.mark.parametrize装饰器一起使用 @pytest.fixture() def fix_demo(request): temp = request.param print("我是{0},我来签到".format(temp)) return temp @pytest.mark.parametrize("fix_demo", ["张三", "李四", "王五"], indirect=True) def test_user(fix_demo): print("{0}已签到".format(fix_demo))
@pytest.fixture(params=["张三", "李四", "王五"], ids=["case01", "case02", "case03"]) def fix_demo(request): temp = request.param print("我是{0},我来签到".format(temp)) return temp def test_user(fix_demo): print("{0}已签到".format(fix_demo)) 运行结果: test_demo_file_01.py::test_user[case01] 我是张三,我来签到 张三已签到 PASSED test_demo_file_01.py::test_user[case02] 我是李四,我来签到 李四已签到 PASSED test_demo_file_01.py::test_user[case03] 我是王五,我来签到 王五已签到 PASSED
yield在python中表示生成器,同时也是一种函数的返回值类型,是函数上下文管理器,使用yield被调fixture函数执行遇到yield会停止执行,接着执行调用的函数,调用的函数执行完后会继续执行fixture函数yield关键后面的代码。
@pytest.fixture() def login_1(): print("开始登录") result = "登录后的token" yield result print("退出登录") def test_demo_01(login_1): a = login_1 print(a) print("执行 test_demo_01 用例") 运行结果: test_demo_file_01.py::test_demo_01 开始登录 登录后的token PASSED退出登录
用例在运行过程可能因为环境原因、网络波动、莫名异常或网络延迟造成用例获取响应失败等。通过引入失败用例重跑机制来消除因为网络不稳定而引起的用例运行失败。
插件官网说明:https://pypi.org/project/pytest-rerunfailures/
安装pytest插件:pip install pytest-rerunfailures 。使用方式有以下两种:
(1)命令行参数形式
pytest --reruns 重试次数
pytest --reruns 重试次数 --reruns-delay 次数之间的延时间隔设置(单位:秒)
# 文件 test_demo_file_01.py
def test_demo_01(login):
r = random.randint(1, 2)
assert r == 1
def test_demo_02(login):
b = 1 + 2
assert b == 3
执行:pytest --reruns 3 --reruns-delay 3 test_demo_file_01.py
(2)装饰器方式
语法:@pytest.mark.flaky(reruns=重试次数, reruns_delay=次数之间的延时设置(单位:秒)) # reruns_delay 非必须
# 文件 test_demo_file_01.py
@pytest.mark.flaky(reruns=3, reruns_delay=3)
def test_demo_01():
r = random.randint(1, 2)
assert r == 1
在实际测试场景中,会遇到功能阻塞、功能未实现或环境等外部因素导致用例无法正常执行的情况,此时可以使用pytest提供的 skip 和 skipif 来跳过用例。
语法:
@pytest.mark.skip() # 跳过类、方法或用例,不备注原因
@pytest.mark.skip(reason=“”) # 跳过类、方法或用例,备注原因
# 文件 test_demo_file_01.py
@pytest.mark.skip(reason="跳过这个用例")
def test_demo_02():
b = 1 + 2
assert b == 3
执行结果:
test_demo_file_01.py::test_demo_02 SKIPPED (跳过这个用例)
语法:@pytest.mark.skipif(contdition, reason=“”) # 满足条件时跳过类、方法或用例,需备注原因
# 文件 test_demo_file_01.py @pytest.mark.skipif(1 == 1, reason="跳过整个测试类") class TestDemo: def test_func_01(self): print("test_func_01") def test_func_02(self, login): print("test_func_02") 运行结果: test_demo_file_01.py::TestDemo::test_func_01 SKIPPED (跳过整个测试类) test_demo_file_01.py::TestDemo::test_func_02 SKIPPED (跳过整个测试类)
语法:@pytest.skip(reason=“”) # 在用例内部执行判断,不满足条件时跳过用例
# 文件 test_demo_file_01.py class TestDemo: def test_func_01(self): a = random.randint(1, 2) if a == 1: pytest.skip(reason="不满足执行条件,跳过该用例") print("test_func_01") def test_func_02(self): print("test_func_02") 运行结果: test_demo_file_01.py::TestDemo::test_func_01 SKIPPED (不满足执行条件,跳过该用例) test_demo_file_01.py::TestDemo::test_func_02 test_func_02 PASSED
语法:pytest.skip(reason=“”, allow_moudle_level=false) # 跳过整个模块,参数必填allow_module_level=True
import pytest pytest.skip(reason="跳过整个模块", allow_module_level=True) class TestDemo: def test_func_01(self): # a = random.randint(1, 2) # if a == 1: # pytest.skip(reason="不满足执行条件,跳过该用例") print("test_func_01") def test_func_02(self): print("test_func_02") 运行结果: Skipped: 跳过整个模块 collected 0 items / 1 skipped
语法:@pytest.importorskip( modname: str, minversion: optional[str] = none, reason: optional[str] = none )
参数解释:
# 测试模块
import pytest
rock = pytest.importorskip("rock")
@rock
def test_1():
print("测试是否导入了rock模块")
# 测试模块版本
import pytest
sel = pytest.importorskip("selenium", minversion="3.150")
@sel
def test_1():
print("测试是否导入了selenium模块")
# 首先定义一个变量
myskip = pytest.mark.skip(reason="未知原因")
myskipif = pytest.mark.skipif(1==1, reason="未知原因")
# 然后在其它模块用例导入这个变量,使用时直接 @myskip @myskipif
@myskip
def test_func_02(self):
print("test_func_02")
在日常测试过程中,当有很多测试用例,但只想执行其中的一部分用例,可以使用@pytest.mark.自定义标签功能满足。
使用mark灵活给测试用例打标签或分组。在运行测试用例的时候,可以根据标签名来过滤运行的用例。
# 1.按环境 @pytest.mark.development @pytest.mark.release ... # 2.按作者 @pytest.mark.zhangsan @pytest.mark.lisi ... # 3.按版本 @pytest.mark.v1 @pytest.mark.v2 ... # 4.按用途(回归、冒烟、全量等) @pytest.mark.smoke @pytest.mark.full ...
测试用例和测试类都支持单标签和多标签。给测试类打标签时,生效的对象是这个测试类下所有的用例。
# 对测试类打标签时可以使用以下两种方式 # 方式一: @pytest.mark.zhangsan @pytest.mark.smoke class TestDemo: def test_func_01(self): print(" excute test_func_01") def test_func_02(self): print("test_func_02") # 方式二: class TestDemo: # 单标签 # pytestmark = pytest.mark.full # 多标签 pytestmark = [pytest.mark.zhangsan, pytest.mark.smoke] def test_func_01(self): print(" excute test_func_01") def test_func_02(self): print("test_func_02")
自定义标签在执行的时候会提示告警信息,如下:
PytestUnknownMarkWarning: Unknown pytest.mark.smoke - is this a typo? You can register custom marks to avoid this warning - for details, see https://docs.pytest.org/en/stable/how-to/mark.html
解决方法一:
在项目的 pytest.ini 文件中添加标签集合,如下:
[pytest]
markers =
smoke: explain for this
success
.....
解决方法二:
在 conftest.py 文件中注册,多个标签添加多条记录,如下:注册 regression、smoke、zhangsan 三个自定义标签
# 固定插件用法
def pytest_configure(config):
config.addinivalue_line("markers", "regression: only regression test")
config.addinivalue_line("markers", "smoke: only smoke test")
config.addinivalue_line("markers", "zhangsan: only author test")
pytest默认执行用例顺序是根据项目下文件名称按ascii码去收集运行的,文件里的用例是从上往下按顺序执行的(模块级会先以模块名按ascii编码进行排序)。
当需要调整测试用例执行顺序时,可以引入插件 pytest-ordering 来控制用例执行顺序。
安装:pip install pytest-ordering
语法:@pytest.mark.run(order=n) # 按照n值顺序执行,order为非负整数(值越小优先级越高) > 无排序装饰器 > order为负整数(负的值越大优先级越高)
@pytest.mark.run(order=3) def test_01(): print("test_01") time.sleep(1.0) @pytest.mark.run(order=2) def test_two(): print("test_two") time.sleep(10) @pytest.mark.run(order=1) def test_regin(): print("用例test_regin") time.sleep(1.5) def test_login(): print("用例login") time.sleep(0.1) def test_05(): print("用例5") time.sleep(2.3) 运行结果: test_demo_file_02.py::test_regin 用例test_regin PASSED test_demo_file_02.py::test_two test_two PASSED test_demo_file_02.py::test_01 test_01 PASSED test_demo_file_02.py::test_login 用例login PASSED test_demo_file_02.py::test_05 用例5 PASSED
安装:pip install pytest-dependency
语法:@pytest.mark.dependency(name=None, depends=[], scope=‘module’)
参数解释:
name ( str) – 用于依赖测试引用的测试名称。如果未设置,则默认为 pytest 定义的节点 ID。名称必须是唯一的。
depends (iterable of str) – 依赖项,该测试所依赖的测试名称列表。除非所有依赖项都已成功运行,否则将跳过测试。依赖项也必须已被标记修饰。依赖项的名称必须适应范围。
scope ( str) – 搜索依赖项的范围。必须是’session’、‘package’、‘module’或’class’。默认是module
创建2个文件 test_depend_01.py、test_depend_02.py,如下:
import pytest @pytest.mark.dependency() def test_a(): pass @pytest.mark.dependency() @pytest.mark.xfail(reason="deliberate fail") def test_b(): assert False @pytest.mark.dependency(depends=["test_a"]) def test_c(): pass @pytest.mark.dependency(depends=['test_b']) def test_d(): pass class TestClass(object): @pytest.mark.dependency() def test_b(self): pass 运行结果: test_depend_01.py::test_a PASSED test_depend_01.py::test_b XFAIL (deliberate fail) test_depend_01.py::test_c PASSED test_depend_01.py::test_d SKIPPED (test_d depends on test_b) # test_d依赖test_b,test_b执行失败,所以test_d跳过执行 test_depend_01.py::TestClass::test_b PASSED
参数化,就是把测试过程中的数据提取出来,通过参数传递不同的数据来驱动用例运行。pytest框架使用装饰器 @pytest.mark.parametrize 来对测试用例进行传参。
语法:@pytest.mark.parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
参数解释:
argnames:参数名称,字符串格式,多个参数用逗号隔开,如:“arg1,arg2,arg3”
argvalues:参数值,需要与argnames对应,元组或列表,如:[ val1,val2,val3 ],如果有多个参数例,元组列表一个元组对应一组参数的值,如:@pytest.mark.parametrize(“name,pwd”, [(“yy1”, “123”), (“yy2”, “123”), (“yy3”, “123”)])
indirect:默认为False,代表传入的是参数。如果设置成True,则把传进来的参数当函数执行,而不是一个参数
ids:自定义测试id,字符串列表,ids的长度需要与测试数据列表的长度一致,标识每一个测试用例,自定义测试数据结果的显示,为了增加可读性
import pytest # fixture函数 @pytest.fixture() def fix_demo(request): temp = request.param print("我是{0},我来签到".format(temp)) return temp # 被测函数 def add(a, b): return a + b # 单个参数参数化 @pytest.mark.parametrize("a", [1, 2, 3, 4, 5]) def test_add_01(a): # 参数名称和个数需和parametrize传入的一致 print("a的值:", a) assert add(a, 1) == a + 1 # 多个参数 @pytest.mark.parametrize("a, b, c", [(1, 2, 3), (4, 5, 9), ('1', '2', '12')]) def test_add_02(a, b, c): # 参数名称和个数需和parametrize传入的一致 print(f"a, b, c的值是:{a}, {b}, {c}") assert add(a, b) == c # 参数组合,场景总数为各参数个数相乘后结果。适用于期望结果是固定的 @pytest.mark.parametrize('x', [0, 1]) @pytest.mark.parametrize('y', [2, 3]) def test_combination(x, y): print(f"测试组合:x={x}, y={y}") # indirect=True, parametrize与request结合使用给fixture传参 @pytest.mark.parametrize("fix_demo", ["张三", "李四", "王五"], indirect=True) def test_user(fix_demo): print("{0}已签到".format(fix_demo)) # 和 pytest.param 联合使用,处理部分用例 @pytest.mark.parametrize("a, b, c", [ (1, 2, 3), pytest.param(2, 3, 6, marks=pytest.mark.xfail), pytest.param('a', 'b', 'ab', marks=pytest.mark.skip), pytest.param(9, 1, 10, id="9+1=10: pass") ]) def test_param(a, b, c): print(f"a, b, c的值是:{a}, {b}, {c}") assert add(a, b) == c
pytest框架使用allure生成测试报告,报告内容丰富美观,功能强大,使用的插件 allure-pytest
安装:pip install allure-pytest
功能特性:
allure使用实例:
import allure from common.logger import logger import requests @allure.step('这是测试步骤') def step_1(): print("初始化数据") @allure.epic('测试天气API接口'.center(30, '*')) @allure.feature('测试模块') @allure.suite('这是套件') class TestHttpbin: """测试模块httpbin""" @allure.severity('normal') @allure.story('故事1:获取天气数据') @allure.title('获取单个城市的天气') @allure.description('获取深圳的天气') @allure.testcase('测试用例地址:www.***.com') @allure.issue('缺陷管理地址:https://www.zentao.net/') @allure.tag('这是tag') def test_001(self, login): """ 测试httpbin接口:get方法 """ step_1() # api:host url = 'https://tianqiapi.com/api' params = {'version': 'v6', 'appid': 12253812, 'appsecret': 'KzT8yOpX'} response = requests.get(url=url, params=params).json() print('接口返回数据: %s' % response) logger.info('接口返回数据: %s' % response)
https://gitlab.dev.21vianet.com/fan.jun3/api-request.git
基本数据流程:读取配置信息–>读取yml测试数据–>生成测试用例–>执行测试用例–>断言–>生成allure报告(–>发送邮件,钉钉推送?)
更多pytest用法参考官方文档文档:https://docs.pytest.org/en/stable/index.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。