同步操作将从 yoyo/pytest-yaml-yoyo 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
基于 httprunner 框架的用例结构,我自己开发了一个 pytest + yaml 的框架,那么是不是重复造轮子呢? 不可否认 httprunner 框架设计非常优秀,但是也有缺点,httprunner3.x 的版本虽然也是基于 pytest 框架设计,结合 yaml 执行用例,但是会生成一个py文件去执行。 在辅助函数的引用也很局限,只能获取函数的返回值,不能在 yaml 中对返回值重新二次取值。 那么我的这个框架,就是为了解决这些痛点。。。。 完整视频教程地址https://study.163.com/course/courseMain.htm?courseId=1213419817&share=2&shareId=480000002230338 (视频收费:包含 pytest + yaml 框架开发教学和使用教学, 买课程的学员可以用新版本更多强大功能,咨询wx:283340479)
本插件可以实现以下优势:
联系我们:
最低版本要求 Python 3.8 版本或以上版本. 目前兼容 python3.8, python3.9, python3.10版本 (低于 python3.8 版本无法安装此插件,低版本python不做适配) Pytest 7.2.0+ 最新版可以有最佳体验
pip 安装插件即可使用,不需要下载源码
pip install pytest-yaml-yoyo
使用 --start-project
命令, 帮助初学者快速创建项目 demo 结构, 并自动创建几个简单的用例。
执行以下命令
pytest --start-project
运行日志
(venv) D:\demo\untitled_start>pytest --start-project
create ini file: D:\demo\untitled_start\pytest.ini
create config file: D:\demo\untitled_start\config.py
create file: D:\demo\untitled_start\case_demo
create yaml file: D:\demo\untitled_start\case_demo\test_get.yml
create yaml file: D:\demo\untitled_start\case_demo\test_post.yml
create yaml file: D:\demo\untitled_start\case_demo\test_extract.yml
执行完成会自动生成以下文件
D:\demo\
├── case_demo/
│ ├── test_extract.yml
│ ├── test_get.yml
│ ├── test_post.yml
├── config.py
├── pytest.ini
test_extract.yml 内容
config:
name: 参数关联-用例a提取结果给到用例b
test_a:
name: extract提取结果
request:
method: POST
url: /post
json:
username: test
password: "123456"
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$..username, test]
- eq: [body.json.username, test]
test_b:
name: 引用上个接口返回
request:
method: GET
url: http://httpbin.org/get
headers:
url: ${url}
validate:
- eq: [status_code, 200]
自动创建 pytest.ini 文件,并添加 2 个配置参数
[pytest]
log_cli = true
env = test
看到项目结构生成后,仅需执行 pytest 命令即可运行用例
pytest
yaml 用例编写规则,跟 pytest 识别默认规则一样,必须是 test 开头的,以 .yml
结尾的文件才会被识别
注意: v1.1.4 之后的新版本可以识别 .yml 和 .yaml 2种后缀格式, 并且是 test 开头或者 test 结尾的文件 四种文件都能被识别成用例:test*.yml 、 test*.yaml 、*test.yml、 *test.yaml
新建一个test_hello.yml
文件
config:
name: yy
teststeps:
-
name: demo
print: hello world
用例整体结构延续了 httprunner 框架的用例结果,主要是为了大家快速上手,减少新的规则学习
从上面的运行可以看出,request 不是必须的,我们可以直接调用python内置函数print 去打印一些内容了。
以http://www.example.com/
get 请求示例
test_get_demo.yml
config:
name: get
teststeps:
-
name: get
request:
method: GET
url: http://www.example.com/
validate:
- eq: [status_code, 200]
命令行输入 pytest 后直接运行
>pytest
======================= test session starts =======================
platform win32 -- Python 3.8.5, pytest-7.2.0, pluggy-1.0.0
rootdir: D:\demo\yaml_yoyo
plugins: yaml-yoyo-1.0.1
collected 2 items
test_get_demo.yml . [ 50%]
test_hello.yml . [100%]
======================== 2 passed in 0.49s ========================
运行规则规则跟 pytest 完全一致,使用 pytest 的命令行运行用例
test_post_demo.yml
config:
name: post示例
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$..username, test]
- eq: [body.json.username, test]
比如返回的response内容
HTTP/1.1 200 OK
Date: Wed, 23 Nov 2022 06:26:25 GMT
Content-Type: application/json
Content-Length: 483
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {},
"data": "{\r\n \"username\": \"test\",\r\n \"password\": \"123456\"\r\n}",
"files": {},
"form": {},
"headers": {
"Content-Length": "55",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "Fiddler",
"X-Amzn-Trace-Id": "Root=1-637dbd11-7d9943ba1fb93a9331f6cf8d"
},
"json": {
"password": "123456",
"username": "test"
},
"origin": "198.187.30.113",
"url": "http://httpbin.org/post"
}
校验可以支持 response 取值对象:status_code, url, ok, headers, cookies, text, json, encoding 其中返回的是 json 格式,那么可以支持
body.keyname.keyname
$..keyname
xx(.+?)xxx
如果返回的不是 json 格式,那么可以用正则取值
变量的声明,只支持在config 声明整个yml文件的全局变量(不支持单个step的变量,减少学习成本)
在 httprunner 里面变量引用语法是$user
, 引用函数是${function()}
我这里统一改成了一个语法变量引用${var}
和 引用函数${function()}
(表面上没多大变量,实际上功能强大了很多,使用了强大的 jinja2 模板引擎)
可以在引用函数后继续对结果操作, 这就解决了很多人提到了函数返回一个 list,如何在 yaml 中取某一个值的问题
config:
name: post示例
variables:
username: test
password: "123456"
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: ${password}
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$..username, test]
- eq: [body.json.username, test]
运行结果
在自动化用例中,我们经常会看到有人提问,上一个接口的返回的结果,如何取出来给到下个接口的入参。 我们用 extract 关键字提取接口的返回结果(需要更新v1.0.2版本)。
举个例子
用个post请求http://httpbin.org/post
POST http://httpbin.org/post HTTP/1.1
User-Agent: Fiddler
Host: httpbin.org
Content-Length: 0
HTTP/1.1 200 OK
Date: Thu, 24 Nov 2022 06:18:03 GMT
Content-Type: application/json
Content-Length: 320
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Content-Length": "0",
"Host": "httpbin.org",
"User-Agent": "Fiddler",
"X-Amzn-Trace-Id": "Root=1-637f0c9a-23b419f4180f6b843ba941af"
},
"json": null,
"origin": "66.112.216.24",
"url": "http://httpbin.org/post"
}
比如我需要提取返回接口里面的url参数,那么我们用extract 关键字
test_demo.yml 文件示例
config:
name: post示例
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$..username, test]
- eq: [body.json.username, test]
上一个接口提取到了url 变量,接下来在下个接口中引用${url}
config:
name: post示例
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$..username, test]
- eq: [body.json.username, test]
-
name: post
request:
method: GET
url: http://httpbin.org/get
headers:
url: ${url}
validate:
- eq: [status_code, 200]
于是看到请求报文中引用成功
GET http://httpbin.org/get HTTP/1.1
Host: httpbin.org
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
url: http://httpbin.org/post
在yaml中对返回值重新二次取值 对于提取的结果,我想继续取值,比如他是一个字符串,在python中可以用切片取值 那么,在 yaml 中如何实现?
我重新设计的这个框架中,就可以支持python语法,直接用切片取值
headers:
url: ${url[:4]}
请求报文
GET http://httpbin.org/get HTTP/1.1
Host: httpbin.org
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate, br
Accept: */*
Connection: keep-alive
url: http
extract 取值语法
校验方式可以支持response取值对象:status_code, url, ok, headers, cookies, text, json, encoding 其中返回的是json格式,那么可以支持
如果返回的不是json格式,那么可以用正则取值
当一个用例用到多组测试数据的时候,我们必然会用到参数化,接下来看下如何在yaml文件中实现参数化
我们在使用自动化测试框架的时候,经常会遇到一个需求,希望在全局用例中,仅登录一次,后续所有的用例自动带上请求头部token 或者cookies。
我在 pytest + yaml 框架框架中封装了一个内置 fixture 叫requests_session
, 它的作用范围是scope="session"
,也就是全部session用例会话中仅实例化一次。
现在我只需在conftest 中写一个登录的fixture功能,获取token后添加到requests_session
头部
import pytest
"""
全局仅登录一次,获取token,
在请求头部添加Authorization Bearer 认证
内置fixture requests_session
"""
@pytest.fixture(scope="session", autouse=True)
def login_first(requests_session):
"""全局仅一次登录, 更新session请求头部"""
# 调用登录方法,获得token
token = "*******************"
headers = {
"Authorization": f"Bearer {token}"
}
requests_session.headers.update(headers)
上面代码中,我用login() 函数实现登录功能,这里返回一个随机值,主要是为了验证我只调用了一次登录方法 接着我写2个yaml文件(注意,yaml文件中也不需要重复去添加请求头部了)
test_get_demo.yml
config:
name: get
teststeps:
-
name: get
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
test_post_demo.yml
config:
name: post示例
variables:
username: test
password: "123456"
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: ${password}
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$..username, test]
- eq: [body.json.username, test]
在命令行中输入pytest
运行
抓包看发过去的请求
于是可以看到,在2个用例中都自动带上了请求头部参数。 (登录cookies的使用原理也是一样的,登录后cookies一般会自动保持)
那有些接口不需要登录怎么办呢?比如登录和注册的接口,是不需要带上登录token的,那不能一刀切。
我除了默认用到一个requests_session
全局的内置fixture,还预留了2个
在yaml文件中切换到指定fixture功能,requests_module
和 requests_function
后续会实现,先实现大需求,解决大部分人遇到的痛点问题!
那有些接口不需要登录怎么办呢?比如登录和注册的接口,是不需要带上登录token的。
除了默认用到一个requests_session 全局的内置fixture,还预留了2个
接下来看下如何在用例中使用test_register.yml
config:
name: post示例
fixtures: requests_module
注册1:
request:
method: POST
url: http://httpbin.org/post
json:
username: test123
password: "123456"
validate:
- eq: [status_code, 200]
注册2:
request:
method: POST
url: http://httpbin.org/post
json:
username: test444
password: "123456"
validate:
- eq: [status_code, 200]
在config 中传入 fixtures参数,requests_module 是每个yaml文件中用一个请求会话(会保持cookies) requests_function 作用是每个用例中用一次,每个用例独立运行,不保持cookies。
pytest 的核心功能是学会灵活使用fixtures, 那么我们的这个插件也是可以支持在用例中调用fixtures功能的。
在conftest.py 文件中写你需要实现的fixture 功能, 设置使用范围为scope="function"
函数级别
import pytest
@pytest.fixture(scope="function")
def demo_fixture():
print("用例前置操作->do something .....")
yield
print("用例后置操作,do something .....")
在 yaml 文件中引用 fixture
config:
name: post示例
fixtures: demo_fixture
注册1:
request:
method: POST
url: http://httpbin.org/post
json:
username: test123
password: "123456"
validate:
- eq: [status_code, 200]
注册2:
request:
method: POST
url: http://httpbin.org/post
json:
username: test444
password: "123456"
validate:
- eq: [status_code, 200]
于是运行结果可以看到,每个用例前后都会执行
collected 2 items
test_f2.yml 用例前置操作->do something .....
.用例后置操作,do something .....
用例前置操作->do something .....
用例后置操作,do something .....
如果想整个yaml 文件中仅运行一次,那么conftest.py 文件中写你需要实现的 fixture 功能, 设置使用范围为scope="module"
模块级别
import pytest
@pytest.fixture(scope="module")
def demo_fixture():
print("用例前置操作->do something .....")
yield
print("用例后置操作,do something .....")
于是看到运行的时候,仅在yaml 文件的全部用例中只执行一次
collected 2 items
test_f2.yml 用例前置操作->do something .....
..用例后置操作,do something .....
当 yaml 中的用例需要用到多个fixtures时, 支持2种格式
格式一: 逗号隔开
config:
fixtures: fixture_name1, fixture_name2
格式二: 用 list
config:
fixtures: [fixture_name1, fixture_name2]
requests_module 和 requests_function 内置 fixture 功能在 v1.1.1 版本实现, 版本太低的请及时更新版本。
当一个用例用到多组测试数据的时候,我们必然会用到参数化,接下来看下如何在yaml文件中实现参数化 用例参数化的实现,我设计了2种实现方式
参数化方式1:
config:
name: post示例
fixtures: username, password
parameters:
- [test1, '123456']
- [test2, '123456']
参数化方式2:
config:
name: post示例
parameters:
- {"username": "test1", "password": "123456"}
- {"username": "test2", "password": "1234562"}
基本实现原理参考 pytest 框架的参数化实现
import pytest
@pytest.mark.parametrize("test_input,expected",
[ ["3+5", 8],
["2+4", 6[,
["6 * 9", 42[,
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
在上面的用例中,只需要关注参数化的2个变量test_input, expected 也就是在测试用例中传的2个值,可以理解为用例的2个fixture参数 还需要关注测试数据,测试数据结构必须是list,里面是可以迭代的数据,因为有2个变量,所以每组数据是2个值。
在yaml文件中
示例 test_params.yml
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
config:
name: post示例
fixtures: username, password
parameters:
- [test1, '123456']
- [test2, '123456']
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: ${password}
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$..username, '${username}']
- eq: [body.json.username, '${username}']
运行yaml文件
pytest test_params.yml
会自动生成2个测试用例
(venv) D:\code\tests>pytest test_params.yml
======================== test session starts ========
platform win32 -- Python 3.8.5, pytest-7.2.0, pluggy-1.0.0
rootdir: D:\code\pytest-yaml-yoyo
plugins: yaml-yoyo-1.0.3
collected 2 items
test_params.yml .. [100%]
=================== 2 passed in 0.77s ================
第二种实现方式,可以在fixtures 中传变量,但是测试数据必须是字典类型,从字典的key中动态读取变量名称 test_params_2.yml
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
config:
name: post示例
parameters:
- {"username": "tes1", "password": "123456"}
- {"username": "tes2", "password": "123456"}
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: ${password}
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$..username, '${username}']
- eq: [body.json.username, '${username}']
运行yaml文件
pytest test_params.yml
以上2种实现参数化方式效果是一样的
pytest-yaml-yoyo 插件使用了强大的jinja2 模板引擎,所以我们在yaml文件中可以写很多python内置的语法了。
举个例子: 我定义了一个变量username的值是test123,但是我引用变量的时候只想取出前面四个字符串,于是可以用到引用变量语法
$(username[:4])
可以直接对变量用python的切片语法
test_fun1.yml
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
config:
name: 引用内置函数
variables:
username: test123
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username[:4]}
password: "123456"
validate:
- eq: [status_code, 200]
- eq: [$..username, test]
命令行执行用例
pytest test_fun1.yml
运行结果
POST http://httpbin.org/post HTTP/1.1
Host: httpbin.org
User-Agent: python-requests/2.28.1
Accept-Encoding: gzip, deflate
Accept: */*
Connection: keep-alive
Content-Length: 42
Content-Type: application/json
{"username": "test", "password": "123456"}
如果定义一个字典类型的变量,我们在取值的时候也可以根据key取值 如定义变量
variables:
username: test123
body:
user: yoyo
email: 123@qq.com
user和email的取值用2种方式,通过点属性
或者用字典取值方法[key]
username: ${body.user}
email: ${body["user"]}
test_fun2.yml完整示例
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
config:
name: 引用内置函数
variables:
username: test123
body:
user: yoyo
email: 123@qq.com
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${body.user}
password: "123456"
email: ${body["user"]}
validate:
- eq: [status_code, 200]
- eq: [$..username, '${body.user}']
一些复杂的逻辑处理,需自己写代码去实现,于是可以自定义函数。
自定义函数的实现,需在conftest.py (pytest 框架内置的插件文件)文件中实现
# conftest.py
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
from pytest_yaml_yoyo import my_builtins
import uuid
import random
def random_user():
"""生成随机账号 4-16位数字+字符a-z"""
return str(uuid.uuid4()).replace('-', '')[:random.randint(4, 16)]
# 注册到插件内置模块上
my_builtins.random_user = random_user
if __name__ == '__main__':
print(random_user())
实现基本原理是自己定义一个函数,然后注册到插件内置模块 my_builtins
。这样我们在用例中就能找到该函数方法了
test_fun3.yml 用例中引用内置函数示例
config:
name: 引用内置函数
variables:
username: ${random_user()}
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: "123456"
validate:
- eq: [status_code, 200]
- eq: [$..username, '${username}']
在引用自定义函数的时候,也可以传变量
# conftest.py
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
from pytest_yaml_yoyo import my_builtins
def func(x):
return f"hello{x}"
my_builtins.func = func
test_fun4.yml示例
config:
name: 引用内置函数
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${func("xxx")}
password: "123456"
validate:
- eq: [status_code, 200]
函数还能引用自己在config 中定义的变量
config:
name: 引用内置函数
variables:
var: test123
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${func(var)}
password: "123456"
validate:
- eq: [status_code, 200]
如果一个函数返回list类型,我们在用例中也能取出其中的一个值
# conftest.py
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
from pytest_yaml_yoyo import my_builtins
def func_list():
return ['test1', 'test2', 'test3']
my_builtins.func_list = func_list
test_fun5.yml示例
config:
name: 引用内置函数
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${func_list().1}
password: "123456"
validate:
- eq: [status_code, 200]
list类型支持2种取值方式${func_list().1}
或者 ${func_list()[1]}
目前暂时提供了3个内置函数,和1个内置对象
还提供了一个内置的fake 对象,可以生成随机手机号,随机身份证,姓名等数据
使用方法:${fake.name()}
, fake.phone_number()
, fake.email()
等,具体查看Faker模块提供的方法https://www.cnblogs.com/yoyoketang/p/14869348.html
current_time() 获取当前时间, 使用示例
获取当前时间:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${current_time()}
password: "123456"
validate:
- eq: [status_code, 200]
rand_value(target: list) 从返回的 list 结果随机取值, 有小伙伴提到的需求
提取list值:
-
request:
method: POST
url: http://httpbin.org/post
json:
data: ["hello", "world", "hello world"]
extract:
res: $.json.data
validate:
- eq: [status_code, 200]
随机取一个结果:
-
request:
method: GET
url: http://httpbin.org/get
params:
key: ${rand_value(res)}
validate:
- eq: [status_code, 200]
rand_str(len_start=None, len_end=None) 生成随机字符串,默认32位
rand_str 使用方法: ${rand_str()} 得到32位字符串 ${rand_str(3)} 得到3位字符串 ${rand_str(3, 10)} 得到3-10位字符串
以上yaml,生成的json数据示例
"json": {
"password": "07d",
"username": "c1c91161b4"
}
内置的 fake 对象 (注意是fake,不是faker, 因为faker 是模块名称,避免冲突) ,可以生成随机手机号,随机身份证,姓名等数据
获取当前时间:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
name: ${fake.name()}
tel: ${fake.phone_number()}
email: ${fake.email()}
validate:
- eq: [status_code, 200]
生成的测试数据
{'name': '王建平', 'tel': 13056609200, 'email': 'jluo@example.net'}
其它更多方法参考Faker模块提供的方法https://www.cnblogs.com/yoyoketang/p/14869348.html
在发送请求的时候,我们希望在发送请求参数前,带上签名的值,或者返回的内容需要二次处理,解密后返回。 此功能我们可以用 hooks 钩子来实现 hooks 功能在v1.0.4版本上实现
requests 库只支持一个 response 的钩子,即在响应返回时可以捎带执行我们自定义的某些方法。 可以用于打印一些信息,做一些响应检查或想响应对象中添加额外的信息
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
import requests
url = 'https://httpbin.org/get'
def response_status(resopnse, *args, **kwargs):
print('url', resopnse.url)
resopnse.status = 'PASS' if resopnse.status_code < 400 else 'FAIL'
res = requests.get(url, hooks={'response': response_status})
print(res.status)
以上是基于requests 库的钩子功能实现的基本方式
在yaml 文件中添加response 钩子功能,跟上面代码方式差不多, 有2种方式
先看单个请求的response 钩子 test_hook1.yml
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
config:
name: post示例
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
hooks:
response: ['hook_response']
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$.code, 0]
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$.code, 0]
在 conftest.py 中注册钩子函数
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
def hook_response(response, *args, **kwargs):
# print(response.text) 原始数据
print("执行response hook 函数内容....")
class NewResponse:
text = '{"code": 0, "data": {"token": "yo yo"}}' # response.text解密
history = response.history
headers = response.headers
cookies = response.cookies
status_code = response.status_code
raw = response.raw
is_redirect = response.is_redirect
content = b'{"code": 0, "data": {"token": "yo yo"}}' # response.text解密
elapsed = response.elapsed
@staticmethod
def json():
# 拿到原始的response.json() 后解码
return {"code": 0, "data": {"token": "yo yo"}}
return NewResponse
my_builtins.hook_response = hook_response
由于上面用例只在第一个请求中使用了hooks功能,所以第二个请求的断言- eq: [$.code, 0]
会失败
func1, func2
,或者是一个list类型[func1, func2]my_builtins
模块response
, 可以重写response内容(如需要对返回结果解密),也可以不用重写 request:
method: POST
url: http://httpbin.org/post
hooks:
response: ['hook_response']
在config 中配置全局hooks功能,格式如下
config:
name: post示例
hooks:
response: ['hook_response']
test_hook2.yml完整示例
config:
name: post示例
hooks:
response: ['hook_response']
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
hooks:
response: ['hook_response']
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$.code, 0]
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$.code, 0]
全局配置hooks, 那么该用例下所有的请求都会带上hooks
如果需要对请求参数预处理,我们还新增了一个request 请求钩子,可以获取到发送请求时的request参数
在conftest.py
# 作者-上海悠悠 微信/QQ交流:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
def func1(req):
print(f'请求预处理:{req}')
def func2():
print(f'请求预处理-----------')
my_builtins.func1 = func1
my_builtins.func2 = func2
在 yaml 文件中使用示例
config:
name: post示例
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
hooks:
request: ['func1', 'func2']
response: ['hook_response']
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$.code, 0]
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
hooks:
response: ['hook_response']
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [headers.Server, gunicorn/19.9.0]
- eq: [$.code, 0]
在config 中设置全局hooks示例
config:
name: post示例
hooks:
request: ['func1', 'func2']
response: ['hook_response']
由于request 变量是 pytest的一个内置fixture,此变量保留着,获取请求参数的函数使用req
代替。
利用request hook功能可以实现请求参数的预处理,比如请求 body 签名和加密处理等需求。
当我们测试流程类的接口,需反复去调用同一个接口,就会想到复用API,在代码里面可以写成函数去调用。 那么在yaml 文件中,我们可以把单个API写到一个yaml 文件,测试用例去调用导入API。
我这里只分2层:API 层 和 Test case 用例层
API 层只做接口的描述,一般放到项目根目录api目录下
api/login.yaml 示例
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: ${username}
password: "123456"
validate:
- eq: [status_code, 200]
如果有需要用到变量,比如登录用户名在不同用例中会用到不同的账号,那么可以使用变量 ${username}
需注意的是,API 层不支持单独运行,因为它只是用例的一个部分,不能当成用例去执行,用例执行需使用 test_*.yml
命名
用例层通过api 关键字导入需要的API,导入的路径是相对路径,需根据项目的根目录去导入。 比如我的项目结构是这样的
├─api
└─ login.yml
├─testcase
└─ test_login.yml
└─conftest.py
└─pytest.ini
那么不管用例文件test_*.yml
在哪个目录,都是以项目根目录去导入API 的yaml文件
config:
name: login case
base_url: http://127.0.0.1:8000
variables:
username: "test123"
password: "123456"
teststeps:
-
name: step login1
api: api/login.yml
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [ok, true]
-
name: step login2
api: api/login.yml
运行用例也是在项目根目录去执行 pytest 运行
pytest testcase
API 层可以引用变量,引用变量的值都是从用例目录的variables 加载的变量,目前只支持config 设置用例全局变量
config:
name: login case
base_url: http://127.0.0.1:8000
variables:
username: "test123"
password: "123456"
我们可以理解为API是用例的一个步骤,是用例的一部分,导入过去相当于复制request 请求到用例步骤里面。
在API 层可以写一些基础的校验,比如校验状态码,我们一般不在API层写业务逻辑校验。 比如登录的用例,期望结果可以是登录成功,也可以是登录失败,那么业务逻辑的校验,应该在用例层去校验
-
name: step login1
api: api/login.yml
extract:
url: body.url
validate:
- eq: [status_code, 200]
- eq: [ok, true]
如果API 层和用例层都有validate 校验,最后会合并到一起校验。
一个yaml 文件中可以写多个用例,yaml 文件相当于py模块,每个用例相当于模块里面定义 pytest 的一个函数, 用例名称最好是test开头,如果不是test开头,也会帮你自动拼接成test开头的
在 pytest 用例中,我们可以在一个模块写多个函数式的用例,每个用例test开头,如下
import pytest
def test1():
"""用例1"""
print("hello 111")
def test2():
"""用例2"""
print("hello 222")
def test3():
"""用例3"""
print("hello 333")
if __name__ == '__main__':
pytest.main(['-s', 'test_sample.py'])
执行后会看到3个用例
collected 3 items
test_sample.py hello 111
.hello 222
.hello 333
.
=============== 3 passed in 0.01s ===========
根据以上 pytest 的基本运行原理,于是我们也可以在yaml文件中写出同等的效果
test1:
name: 用例1
print: hello 11111
test2:
name: 用例2
print: hello 22222
test3:
name: 用例3
print: hello 3333
输入 pytest 运行 yaml 用例文件
(venv) D:\demo>pytest test_case.yml -s
=================================== test session starts ===================================
platform win32 -- Python 3.8.5, pytest-7.2.0, pluggy-1.0.0
collected 3 items
test_case.yml hello 11111
.hello 22222
.hello 3333
.
==================================== 3 passed in 0.15s ====================================
可以看出执行效果是完全一样的
为了框架的可扩展性,config 和 teststeps 都不是必须的了,当然以前的格式还是会兼容
config:
name: demo
teststeps:
-
name: GET请求示例
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
test1:
name: 用例1
print: hello 11111
test2:
name: 用例2
print: hello 22222
用例部分支持2种格式,可以是一个键值对格式
test1:
name: 用例1
print: hello 11111
也可以是一个list
test1:
-
name: 用例1
print: hello 11111
如果一个用例有多个步骤需要执行,那么用例应该是一个list,会按顺序去执行
config:
name: demo
test1:
name: 用例1
print: hello 11111
test2:
-
name: get
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
validate:
- eq: [status_code, 200]
用例的函数名称也可以使用中文命名了,这样更直观
config:
name: demo
用例演示1:
name: 用例1
print: hello 11111
用例是多个步骤2:
-
name: get
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
validate:
- eq: [status_code, 200]
原有的用例规则不变,只是teststeps
不是必须的关键字,可以用其它的名称,也可以继续使用teststeps
(为了兼容大家的使用习惯)
v1.1.3版本上实现文件上传
本插件集成了 requests_toolbelt 插件处理Content-Type: multipart/form-data
类型文件上传接口。
用fiddler抓包,查看抓到的接口,以下这种接口就是multipart/form-data
-----------------------------22165374713946
Content-Disposition: form-data; title="localUrl"
yoyoketang.png
-----------------------------22165374713946
Content-Disposition: form-data; name="imgFile"; filename="yoyoketang.png"
Content-Type: image/png
在postman 中,可以直接选择一个文件上传,非常方便
我们在yaml中也一样,支持文件类的参数,需单独拿出来放到 files 字段里面。
test_upfile.yml 示例
文件上传:
name: upload file
request:
url: http://127.0.0.1:8000/api/v1/upfile/
method: POST
data:
title: 文件上传
files:
file: data/abc.jpg
文件abc.jpg 需放到项目根目录data下
files 里面需要传的具体字段,需根据接口文档定义的参数名称。
当然你把其它字符串字段一起放到files 里面也没问题
文件上传:
name: upload file
request:
url: http://127.0.0.1:8000/api/v1/upfile/
method: POST
files:
title: 文件上传
file: data/abc.jpg
(本插件也是根据你是否在 request 中传了 files 字段来判断是不是需要上传文件)
环境要求
Python 大于等于3.8版本,(低于python3.8版本不支持)
Pytest 7.2.0 最新版
v1.1.4 发布新增3个关键字
pip 安装插件, 最新版本v1.1.4
pip install pytest-yaml-yoyo
sleep 功能实现time.sleep() 等待时间,sleep参数可以是int类型和float 类型
get请求:
name: GET请求示例
sleep: 5
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
sleep 也可以是一个变量,引用config设置的变量值
config:
variables:
x: 2.5
get请求:
name: GET请求示例
sleep: ${x}
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
sleep 执行顺序按写的顺序执行,如果放到request 前,那就在请求前先执行,放到request后,在请求后执行
pytest 实现跳过用例有2种方式
@pytest.mark.skip(reason="no way of currently testing this")
def test_the_unknown():
...
或者在用例里面跳过
import pytest
if not pytest.config.getoption("--custom-flag"):
pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True)
本插件采用的第二种实现方式,在用例里面添加pytest.skip()
skip 关键字后仅支持一个参数,那就是跳过的原因。使用示例
get请求:
name: GET请求示例
skip: 功能缺失,暂时跳过此用例
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
运行用例可以看到
如果用例是多个步骤组成的,也可以在步骤中跳过
teststeps:
-
name: step1--
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
-
name: step2--
skip: 功能缺失,暂时跳过此用例
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
那么会按顺序执行,第一个步骤会执行,第二个步骤因为有skip,就跳过了
如果还有第三个步骤,那么一旦遇到skip ,整个用例就会结束,skip 跳过的是用例,而不是步骤!
skipif 后面参数是一个表达式,当表达式运行结果为真,那么就跳过用例
config:
variables:
x: 100
get请求:
name: get
skipif: ${x} > 50
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
skipif 还可以在多个用例中使用,当前面接口返回数据a,判断a满足条件就跳过后面用例
case1:
name: get
request:
method: GET
url: http://httpbin.org/get
extract:
xx: $.url
validate:
- eq: [status_code, 200]
case2:
name: get
skipif: "'org' in '${xx}'"
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
skikif 后面的参数需是字符串表达式,通过eval()函数运行后得到结果,判断是否为真,为真的时候通过当前用例。
pytest 的日志分2个部分:
本插件默认情况下会记录运行日志保存在项目根目录logs下,以当前时间保存txt文本日志内容。 日志默认保存info级别。 console 控制台默认不输出日志
控制台直接运行 pytest 是不会用日志输出的,因为默认仅输出 warning 以上的级别日志 有3种方式启动console日志
方法1:命令行带上--log-cli-level
参数,设置日志级别
>pytest --log-cli-level=info
方法2: pytest.ini 配置开启日志,并且设置日志级别
[pytest]
log_cli = true
log_cli_level = info
方法3: pytest -o方式重写(即覆盖ini文件中的log相关的命令行参数)
pytest -o log_cli=true -o log_cli_level=INFO
即可在控制台看到日志
-------------------------------------------- live log call --------------------------------------------
2022-12-08 08:30:34 [INFO]: 执行文件-> test_demo.yml
2022-12-08 08:30:34 [INFO]: base_url-> None
2022-12-08 08:30:34 [INFO]: variables-> {}
2022-12-08 08:30:34 [INFO]: 运行 teststeps
2022-12-08 08:30:34 [INFO]: -------- request info ----------
POST http://httpbin.org/post
{
"method": "POST",
"url": "http://httpbin.org/post",
"json": {
"username": "test",
"password": "123456"
}
}
2022-12-08 08:30:35 [INFO]: ------ response info 200 OK 0.495961s------
日志的格式和时间格式也可以自定义设置
[pytest]
log_cli = true
log_cli_level = info
log_cli_format = %(asctime)s %(filename)s:%(lineno)s [%(levelname)s]: %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
本插件默认情况下会记录运行日志保存在项目根目录logs下,以当前时间保存txt文本日志内容。 日志默认保存info级别。 如果你想改变这些默认的行为,自定义日志文件目录和名称,可以在pytest.ini 配置日志文件 (log_file 相关的结果是保存日志文件到本地)
[pytest]
log_cli = true
log_cli_level = info
log_cli_format = %(asctime)s %(filename)s:%(lineno)s [%(levelname)s]: %(message)s
log_cli_date_format = %Y-%m-%d %H:%M:%S
log_file = ./yoyo.log
log_file_level = debug
log_file_format = %(asctime)s %(filename)s:%(lineno)s [%(levelname)s]: %(message)s
log_file_date_format = %Y-%m-%d %H:%M:%S
log日志的配置也可以用命令行参数配置(pytest -h可以查看)
--no-print-logs disable printing caught logs on failed tests.
--log-level=LOG_LEVEL logging level used by the logging module
--log-format=LOG_FORMAT log format as used by the logging module.
--log-date-format=LOG_DATE_FORMAT log date format as used by the logging module.
--log-cli-level=LOG_CLI_LEVEL cli logging level.
--log-cli-format=LOG_CLI_FORMAT log format as used by the logging module.
--log-cli-date-format=LOG_CLI_DATE_FORMAT log date format as used by the logging module.
--log-file=LOG_FILE path to a file when logging will be written to.
--log-file-level=LOG_FILE_LEVEL log file logging level.
--log-file-format=LOG_FILE_FORMAT log format as used by the logging module.
--log-file-date-format=LOG_FILE_DATE_FORMAT log date format as used by the logging module.
还可以使用 pytest -o
方式重写(即覆盖 ini 文件中的 log 相关的命令行参数)
pytest pytest test_log.py -o log_cli=true -o log_cli_level=INFO
本插件是基于 pytest 框架开发的,所以 pytest 的插件都能使用 allure 报告功能在 v1.0.8 版本上实现
allure 是一个命令行工具,需要去github上下载最新版https://github.com/allure-framework/allure2/releases
allure 命令行工具是需要依赖jdk 环境,环境内容自己去搭建了
在用例所在的目录执行命令, --alluredir
是指定报告生成的目录
pytest --alluredir ./report
打开allure 报告执行命令
>allure serve ./report
首页显示
图表查看
用例详情根据yaml文件名称和用例名称展示内容
一个完整的url 地址由环境地址和接口地址拼接而成,环境地址是可变的,可以部署到测试环境,uat联调环境等不同的环境。 不管部署到哪个环境,接口的地址是不可变的,通常需要一个全局base_url 地址做到环境可切换。 pip 安装插件
pip install pytest-yaml-yoyo
base_url 全局配置功能在 v1.0.9 版本上实现
在接口测试中,通常会把环境 base_url 地址独立出来
比如一个完整的请求http://httpbin.org/get
那么可以分成环境地址http://httpbin.org
和 接口地址 /get
在 yaml 用例中,可以把 base_url 单独拿出来放到 config 下
config:
base_url: http://httpbin.org
get示例:
name: get demo
request:
method: GET
url: /get
validate:
- eq: [status_code, 200]
post示例:
name: get demo
request:
method: POST
url: /post
validate:
- eq: [status_code, 200]
从项目的角度讲,测试项目接口的 base_url 都是一样的,所以我们只需全局设置一个就行了,不需要每个yaml 文件中重复去写。 于是可以在pytest.ini 里面配置全局base_url
[pytest]
base_url = http://httpbin.org
那么yaml用例就不需要写 base_url 了,默认会引用pytest.ini的全局配置
yaml 用例1
config:
name: demo1
get示例:
name: get demo
request:
method: GET
url: /get
validate:
- eq: [status_code, 200]
yaml 用例2
config:
name: demo2
post示例:
name: get demo
request:
method: POST
url: /post
validate:
- eq: [status_code, 200]
除了可以在pytest.ini 配置base_url 参数,也可以通过命令行参数--base-url
去设置
pytest --base-url=http://httpbin.org
当设置了全局base_url 后,有部分用例的环境地址不是同一个的时候,我们可以在yaml文件中config 配置 base_url 去覆盖全局配置环境地址。
config:
base_url: http://httpbin.org
get示例:
name: get demo
request:
method: GET
url: /get
validate:
- eq: [status_code, 200]
或者请求 url 地址用绝对地址
config:
name: demo
get示例:
name: get demo
request:
method: GET
url: http://httpbin.org/get
validate:
- eq: [status_code, 200]
环境地址优先级使用如下:
1.全局配置命令行参数--base-url
优先级大于 pytest.ini 文件中的 base_url 配置。
2.yaml 文件 config 中的base_url 优先级大于全局配置
3.request 请求的url 如果是绝对地址,那么base_url 无效
总的来说 : url 绝对地址 > config 中的base_url > pytest.ini 文件中的base_url > 命令行参数--base-url
当我们在测试环境写好自动化的代码,领导说你把代码部署到联调环境再测一测,这时候去改用例里面的配置是很痛苦的。 所以我们在设计自动化用例的时候,就先要想到多环境的配置与切换。
如果需用到多套环境 test/uat 等,那么应该在用例的根目录(pytest.ini 同级文件)创建一个config.py 文件 pip 安装插件
pip install pytest-yaml-yoyo
多套环境切换功能在 v1.0.10 版本上实现
class Config:
"""多套环境的公共配置"""
version = "v1.0"
class TestConfig(Config):
"""测试环境"""
BASE_URL = 'http://192.168.1.1:8000'
MYSQL_HOST = "192.168.1.1"
MYSQL_USER = "root"
MYSQL_PASSWORD = "123456"
MYSQL_PORT = 3306
MYSQL_DATABASE = "xxx" # 连接数据的库名
class UatConfig(Config):
"""联调环境"""
BASE_URL = 'http://192.168.1.3:8080'
MYSQL_HOST = "http://192.168.1.3"
MYSQL_USER = "root"
MYSQL_PASSWORD = "654321"
MYSQL_PORT = 3306
MYSQL_DATABASE = "xxx" # 连接数据的库名
# 环境关系映射,方便切换多环境配置
env = {
"test": TestConfig,
"uat": UatConfig
}
按以上的配置格式,配置不同的环境,最后做一个环境名称和配置的映射关系,必须是 env 命名,格式如下
env = {
"test": TestConfig,
"uat": UatConfig
}
那么在执行用例的时候,可以选择执行test 环境还是uat 环境,有 2 种方式可以配置待执行的环境
方法一: 在pytest.ini 中配置
[pytest]
env = test
方法二: 执行 pytest 命令的时候设置
pytest --env test
如果2个地方都有设置,那么优先级是:命令行参数 --env test
大于 pytest.ini 中配置env = test
.
在上一篇中讲到 pytest + yaml 框架 -11.全局 base_url 配置
环境地址优先级使用如下:
1.全局配置命令行参数--base-url优先级大于 pytest.ini 文件中的base_url 配置。
2.yaml 文件 config 中的base_url 优先级大于全局配置
3.request 请求的url 如果是绝对地址,那么base_url 无效
总的来说 : url 绝对地址 > config 中的base_url > 命令行参数--base-url > pytest.ini 文件中的base_url
这里我们新增了一个在config.py 中也可以配置全局的base_url (config.py 中的配置用大写命名 BASE_URL)
如果在 config.py 中配置全局的 BASE_URL ,那么也会生效。优先级会低于命令行和 pytest.ini 的配置
总的来说:url 绝对地址 > config 中的base_url > 命令行参数--base-url > pytest.ini 文件中的 base_url > config.py 的 BASE_URL
有同学提到说,如果一个用例中有多个base_url 需要切换,该如何解决?
我们在配置项中BASE_URL 项是设置默认的全局base_url地址,如果有多个地址,我们还可以用其它的配置参数,比如 BLOG_URL
和 DEMO_URL
class TestConfig(Config):
"""测试环境"""
BASE_URL = 'http://192.168.1.1:8000'
BLOG_URL = 'https://www.cnblogs.com'
DEMO_URL = 'http://httpbin.org'
MYSQL_HOST = "192.168.1.1"
MYSQL_USER = "root"
MYSQL_PASSWORD = "123456"
MYSQL_PORT = 3306
MYSQL_DATABASE = "xxx" # 连接数据的库名
上面的配置中,我们就配置了3个环境地址
BASE_URL = 'http://192.168.1.1:8000'
BLOG_URL = 'https://www.cnblogs.com'
DEMO_URL = 'http://httpbin.org'
在yaml 用例中,如果没有传base_url, 那么会用默认的 BASE_URL = 'http://192.168.1.1:8000'
使用示例
config:
name: 示例
用例:
-
name: GET
request:
method: GET
url: /get
validate:
- eq: [status_code, 200]
当一个接口中同时用到3个测试地址 BASE_URL
、BLOG_URL
、DEMO_URL
时,可以通过 env 变量取值,如: ${env.BLOG_URL}
config:
name: 示例
用例:
-
name: GET
request:
method: GET
url: ${env.BLOG_URL}/yoyoketang
validate:
- eq: [status_code, 200]
-
name: GET
request:
method: GET
url: ${env.DEMO_URL}/get
validate:
- eq: [status_code, 200]
-
name: GET
request:
method: GET
url: /get
validate:
- eq: [status_code, 200]
如果配置中需要加其它配置参数
class TestConfig(Config):
"""测试环境"""
BASE_URL = 'http://192.168.1.1:8000'
USER = 'test'
TEL = '100860000'
那么在用例中引用配置参数可以用 ${env.配置参数}
取到配置中的值。
如果用例中需要执行mysql 数据库,或者在断言的时候需要查询mysql 数据库。先在config.py 中完成配置
class TestConfig(Config):
"""测试环境"""
BASE_URL = 'http://192.168.1.1:8000'
MYSQL_HOST = "192.168.1.1"
MYSQL_USER = "root"
MYSQL_PASSWORD = "123456"
MYSQL_PORT = 3306
MYSQL_DATABASE = "xxx" # 连接数据的库名
当完成了MYSQL 相关的五个配置,那么有个内置的函数可以使用
使用示例
config:
base_url: http://192.168.1.1:8000
variables:
username: test
sql: select * from auth_user where username like 'test';
登录:
name: step login
request:
url: /api/v1/login
method: POST
json:
username: ${username}
password: "123456"
extract:
token: $.token
validate:
- eq: [status_code, 200]
- eq: [ok, true]
- eq: [$.username, '${query_sql(sql).username}']
以上示例是断言的时候,执行sql,获取数据库的值
- eq: [$.username, '${query_sql(sql).username}']
可以开启日志
[pytest]
log_cli = true
log_cli_level = debug
env = test
查看运行日志
body:
{"code": 0, "msg": "login success!", "username": "test", "token": "6112772900193da079e9fcc857613f6125
3648fd"}
2022-12-13 10:34:54 [INFO]: extract 提取变量-> {'token': '6112772900193da079e9fcc857613f61253648fd'}
2022-12-13 10:34:54 [DEBUG]: query sql: select * from auth_user where username like 'test';!
2022-12-13 10:34:54 [INFO]: query result: {'id': 2, 'password': 'pbkdf2_sha256$100000$rSQNBkIc2xOm$VGXiUZk
dsIueT/AsoPwlFSEL1vGODsK7eIjK0nawH/M=', 'last_login': None, 'is_superuser': 0, 'username': 'test', 'first_
name': '', 'last_name': '', 'email': '478391@qq.com', 'is_staff': 0, 'is_active': 1, 'date_joined': dateti
me.datetime(2022, 11, 11, 21, 22, 59, 971425)}
2022-12-13 10:34:54 [INFO]: validate 校验内容-> [{'eq': ['status_code', 200]}, {'eq': ['ok', True]}, {'eq'
: ['$.username', 'test']}]
2022-12-13 10:34:54 [INFO]: validate 校验结果-> eq: [200, 200]
2022-12-13 10:34:54 [INFO]: validate 校验结果-> eq: [True, True]
2022-12-13 10:34:54 [INFO]: validate 校验结果-> eq: [test, test]
从返回的body 里面提取username 使用表达式$.username
, 得到实际结果"test"
'${query_sql(sql).username}' 表达式会先调用query_sql(sql) 函数,引用前面设置的变量sql, 得到结果
{'id': 2, 'password': 'pbkdf2_sha256$100000$rSQNBkIc2xOm$VGXiUZk
dsIueT/AsoPwlFSEL1vGODsK7eIjK0nawH/M=', 'last_login': None, 'is_superuser': 0, 'username': 'test', 'first_
name': '', 'last_name': '', 'email': '478391@qq.com', 'is_staff': 0, 'is_active': 1, 'date_joined': dateti
me.datetime(2022, 11, 11, 21, 22, 59, 971425)}
得到的结果是一个字典,字典对象可以继续取值,于是'${query_sql(sql).username}'
就可以得到期望结果 "test"
如果用例的参数,需要从sql中取值,我们也可以先定义变量,在用例中引用query_sql(sql) 函数
config:
variables:
sql: select * from auth_user where username like 'test';
登录:
name: step login
request:
url: /api/v1/login
method: POST
json:
username: ${query_sql(sql).username}
password: "123456"
extract:
token: $.token
x: ${query_sql(sql).username}
validate:
- eq: [status_code, 200]
- eq: [ok, true]
- eq: [$.username, test]
extract 中也可以支持执行sql,得到提取结果
extract:
token: $.token
x: ${query_sql(sql).username}
如果需要在用例的前置和后置中执行sql, 可以用到hook 机制,在请求前和请求后执行函数 参考前面这篇pytest + yaml 框架 -6.hooks 钩子功能实现
在实际的工作中,有些系统的接口我们无法直接访问,需使用代理去访问,那么就需要在整个项目的用例中配置一个全局代理ip
环境要求
Python 大于等于3.8版本,(低于python3.8版本不支持)
Pytest 7.2.0 最新版
pip 安装插件, 最新版本v1.1.6,此功能在v1.1.6版本上实现
pip install pytest-yaml-yoyo
支持2种方式实现
1.在命令行执行的时候带上 --proxies-ip=代理ip:端口
>pytest test_xxx.yml --proxies-ip=127.0.0.1:8080
2.可以在pytest.ini 添加全局配置
[pytest]
proxies_ip = 127.0.0.1:8080
注意配置的ip和端口,前面的 http/https 前缀不需要
test_pp.yaml 用例文件中不需要添加额外的参数
config:
name: post示例
teststeps:
-
name: post
request:
method: POST
url: http://httpbin.org/post
json:
username: test
password: "123456"
方式1:命令行运行
> pytest test_pp.yml --proxies-ip=127.0.0.1:8080
方式2:使用 pytest.ini 添加全局配置
[pytest]
proxies_ip = 127.0.0.1:8080
当用例执行完成后,希望能给报告反馈,常见的报告反馈有:邮箱/钉钉群/飞书/企业微信 等。 pip 安装插件
pip install pytest-yaml-yoyo
钉钉机器人通知测试结果功能在v1.1.1版本实现
钉钉机器人的设置请参考官方API文档https://open.dingtalk.com/document/group/custom-robot-access
我们主要得到Webhook地址上面的access_token 值
自定义关键字,默认:测试报告,也可以自定义
加签 的值,可以不勾选,也可以勾选。
总的来说,需要记住3个地方:
在config 中配置 DING_TALK, 只有 access_token 值是必须项, 如果配置了 DING_TALK ,那么就会自动启动发送钉钉机器人通知。 如果不启动钉钉机器人通知测试报告,那么把此项注掉即可。
class Config:
version = "v1.0"
class TestConfig(Config):
"""测试环境"""
BASE_URL = 'http://127.0.0.1:8000'
# 钉钉群机器人通知
DING_TALK = {
"access_token": "d2433d2b16cc85*************************************",
}
class UatConfig(Config):
"""联调环境"""
BASE_URL = 'http://192.168.1.1:8001'
# 环境关系映射,方便切换多环境配置
env = {
"test": TestConfig,
"uat": UatConfig
}
在pytest.ini 中配置
[pytest]
env = test
DING_TALK 相关参数说明
运行用例后会自动在钉钉群发送通知
如果这里没有勾选 加签 值
那么只需要配置一个 access_token 即可
DING_TALK = {
"access_token": "d2433d2b16cc85943*********************************",
}
如果这里勾选 加签 值, 那么需同时配置 access_token 和 secret 值
DING_TALK = {
"access_token": "d2433d2b16cc85943*********************************",
"secret": "**************************"
}
奈特指定的人有3个配置可以选择
使用示例
DING_TALK = {
"access_token": "d2433d2b16cc85****************",
"at_mobiles": ["15000xxxxxxx", "15001xxxxxxx"]
}
于是就可以看到上图的效果,在内容后面带上@张三
的样式
title 的名称必须与自定义关键字名称保持一致
DING_TALK = {
"access_token": "d2433d2b16******************",
"title": "测试报告",
"at_mobiles": ["15000xxxxxxx", "15001xxxxxxx"]
}
报告的 text 内容, 也就是我们看到的
执行结果:
- 运行环境: test
- 运行base_url: http://127.0.0.1:8000
- 持续时间: 0.37 秒
本次运行结果:
- 总用例数: 3
- 通过用例:3
- 失败用例: 0
- 异常用例: 0
- 通过率: 100.0 %
text 的内容,默认是上面的这些,支持markdown 文档格式,如果你需要添加额外的内容,比如加上allure报告地址,那么可以用 text 字段追加内容
DING_TALK = {
"access_token": "d2433d2b16cc8594348**************",
"title": "测试报告",
"at_mobiles": ["15000xxxxxxx", "15001xxxxxxx"],
"text": ""text": "- 查看报告:[allure报告地址](https://www.cnblogs.com/yoyoketang/)""
}
把上面的https://www.cnblogs.com/yoyoketang/
换成你自己的allure报告地址即可
于是看到以下的效果
总的来说,整个配置都是傻瓜式的,配置非常简单。
当项目中有很多个接口的时候,一个个去转成 yaml 文件的用例会很浪费时间,现在大部分格式的接口都有swagger.json 接口文档。 那么我们可以从swagger.json 中解析出接口,自动生成 yaml 格式的用例,就可以大大减少工作量。 此功能在 v1.1.5 版本上实现
环境要求 Python 大于等于3.8版本,(低于python3.8版本不支持) Pytest 7.2.0 最新版
pip 安装插件, 最新版本v1.1.5
pip install pytest-yaml-yoyo
目前支持2中方式生成 yaml 用例。 1.如果有本地的 swagger.json 文件,可以放到项目根目录,自己写 a.py 文件 目录结构如下
a.py 文件调用插件中的接口即可
from pytest_yaml_yoyo.swagger_parser import SwaggerToYaml
# 作者 上海-悠悠 微信:283340479
s = SwaggerToYaml('./swagger.json')
s.parse_json()
a.py 文件调用插件中的接口即可
from pytest_yaml_yoyo.swagger_parser import SwaggerToYaml
# 作者 上海-悠悠 微信:283340479
s = SwaggerToYaml('http://127.0.0.1:8000/swagger.json')
s.parse_json()
执行完成后会在当前项目目录按接口模块生成对应的yaml格式用例
yaml 文件格式示例
生成的用例没有base_url ,只有接口的相对地址,那么可以在当前目录下创建pytest.ini 文件
[pytest]
base_url = http://httpbin.org
相关 功能参考全局 base_url 的设置文档https://www.cnblogs.com/yoyoketang/p/16970491.html
备注说明: 1.并不是所有的swagger.json格式都支持,目前我是按flask项目生成的swagger.json 格式来解析的,其它的未知,思路供学习和参考。 2.目前只实现基础功能,需写上面代码调用此功能,暂不支持命令行操作 3.目前仅仅是抓取接口和请求参数,自动生成用例结构 4.参数部分拿文档的默认值,还需自己去调试,写对应的测试数据 5.后续想法是根据参数的范围,按等价类,边界值去生成对应的用例 6.断言部分功能未实现,目前仅断言状态码为200
付费用户可获取最新版本功能,和版本升级服务
录制功能环境没给大家预装,考虑大家 python 版本不太一样,可能有的人装不上。官方文档地址https://docs.mitmproxy.org/stable/
1.先需要准备mitmproxy
环境,最好是 python3.9 版本, 使用 pip 安装接口
pip install mitmproxy
2.安装完成后在项目本地新建一个recorde.py
,名称随便定义
from pytest_yaml_yoyo.mitm_http import RecoderHTTP
"""
步骤:
1.pip 安装 mitmproxy 环境
> pip install mitmproxy
2.复制这里的代码,新建recorde.py 文件,设置过滤环境如:http://127.0.0.1:8001
3.启动服务
> mitmweb -s ./recorde.py -p 8099
4.电脑开启代理,设置对应端口
5.自动录制抓包转成 yaml 用例
"""
addons = [
RecoderHTTP(['http://你需要抓的环境地址:8001']) # 设置过滤环境
]
3.执行命令启动服务, 指定监听 8099 端口
mitmweb -s ./recorde.py -p 8099
启动后我们会看到浏览器打开抓包页面
4.电脑开启代理,设置对应端口
浏览器-设置-系统-打开您计算机的代理设置 开启代理-设置8099打开-并点保存 保存后就可以开始抓包了,电脑上发出去的请求都能抓到,比如浏览器打开你要测试的地址,或者通过python写的接口脚本, postman 上执行的接口都能抓到
5.自动抓包生成 yaml 用例 抓到接口会自动生成 yaml 格式用例,如下
config:
base_url: http://httpbin.org
post_post:
request:
method: POST
url: /post
headers:
Content-Type: application/json
json:
user: test
password: '123456'
validate:
- eq: [status_code, 200]
- eq: [headers."Content-Type", application/json]
- eq: [$.data, '{"user": "test", "password": "123456"}']
- eq: [$.origin, 183.193.25.125]
- eq: [$.url, http://httpbin.org/post]
RecoderHTTP
实例化时,可以设置以下几个参数
1.默认情况下,只需传一个参数,抓取的环境地址,可以是一个,也可以是多个
addons = [
RecoderHTTP(['http://httpbin.org'])
]
也可以抓取多个环境地址
addons = [
RecoderHTTP(['http://httpbin.org', 'https://www.baidu.com'])
]
2.ignore_cookies 是设置是否忽略cookies抓取,默认False
设置为True, 录制的yaml 用例中不会带上cookies
3.save_base_url
是否在 pytest.ini 保存全局base_url环境地址, 默认False.
默认情况下,每个yaml 用例中在config 添加base_url 环境地址,兼容抓取多个环境的情况
如果只需抓一个环境的地址,设置全局base_url地址,设置save_base_url=True
addons = [
RecoderHTTP(['http://httpbin.org'], save_base_url=True)
]
抓取用例时会自动创建 pytest.ini 文件
[pytest]
log_cli = true
base_url = http://httpbin.org
用例抓取完成后,重新打开一个终端窗口,输入 pytest 命令就能执行用例了
前面已经通过代理实现了抓包自动生成 yaml 用例的功能,通过代理也可以实现 mock 功能。 mock 有2种场景: 1.直接拦截发出去的请求,还未到达服务端,模拟自定义返回结果 2.发出去的请求,服务端有反回,拦截返回的结果,篡改返回内容,模拟自己需要的数据
先看第一种场景:直接拦截发出去的请求,还未到达服务端,模拟自定义返回结果
mt_mock.py 内容如下
from mitmproxy import http
class MockAPI:
def request(self, flow: http.HTTPFlow):
print("------------拦截请求----------------")
if flow.request.pretty_url == "https://www.cnblogs.com/yoyoketang/":
# 构造自定义 response
flow.response = http.Response.make(
200, # 返回状态码
"自定义返回内容: 上海-悠悠", # 返回content str or bytes
{"Content-Type": "text/html"} # 返回 headers
)
addons = [
MockAPI()
]
启动服务
>mitmweb -s ./mt_mock.py -p 8099
本机开启代理,设置8099端口。
浏览器访问https://www.cnblogs.com/yoyoketang/
地址,就会看到模拟的返回结果
第二种场景:发出去的请求,服务端有反回,拦截返回的结果,篡改返回内容,模拟自己需要的数据
使用示例:访问http://www.example.com/
本来返回的是html格式,我改下返回json格式
from mitmproxy import http
class MockAPI:
def request(self, flow: http.HTTPFlow):
print("------------拦截请求----------------")
if flow.request.pretty_url == "https://www.cnblogs.com/yoyoketang/":
# 构造自定义 response
flow.response = http.Response.make(
200, # 返回状态码
"自定义返回内容: 上海-悠悠", # 返回content str or bytes
{"Content-Type": "text/html"} # 返回 headers
)
def response(self, flow: http.HTTPFlow):
if flow.request.pretty_url == "http://www.example.com/":
# 修改返回结果
print(f'状态码: {flow.response.status_code}')
flow.response.headers["Content-Type"] = "application/json"
flow.response.set_text('{"code": 0, "message": "success"}')
addons = [
MockAPI()
]
pytest可以支持对用例自定义标记, 可以把用例按自己的需要归类标记,比如按用例优秀级,标记一些smoke冒烟测试用例。
test_m.py 用例内容
import pytest
@pytest.mark.smoke
def test_login():
pass
def test_something():
pass
@pytest.mark.smoke
def test_another():
pass
执行的时候加-m 参数
(venv) D:\demo\untitled_mark>pytest test_m.py -m smoke
================== test session starts ====================
platform win32 -- Python 3.8.5, pytest-7.3.1, pluggy-1.0.0
rootdir: D:\demo\untitled_mark
configfile: pytest.ini
plugins: allure-pytest-2.13.2, Faker-18.10.1, yaml-yoyo-1.3.0
collected 3 items / 1 deselected / 2 selected
test_m.py .. [100%]
================ 2 passed, 1 deselected in 0.13s ================
yaml 用例中支持2个地方加标记
需注意的是一个用例可以打多个标记,mark 关键字可以是一个字符串,如果是多个标记,可以用mark: name1, name2
或 mark: [name1, name2]
两种格式
test_m.yml
config:
name: yy
mark: www
test_a1:
name: a11
print: "xx111"
test_a2:
name: a22
print: "xx22"
config 中加标记,对test_a1 和 test_a2 都会打上标记
test_n.yml
config:
name: yy2
test_a3:
mark: [www, aaa]
name: a333
print: "xx333"
test_a4:
-
name: a444
mark: aaa
print: "xx444"
1.执行标记为 www 的用例
>pytest -m www -s
运行结果
collected 4 items / 1 deselected / 3 selected
test_m.yml xx111
.xx22
.
test_n.yml xx333
.
================ 3 passed, 1 deselected in 0.31s ================
2.执行标记为 aaa 的用例
>pytest -m aaa -s
运行结果
collected 4 items / 2 deselected / 2 selected
test_n.yml xx333
.xx444
.
================= 2 passed, 2 deselected in 0.31s =================
3.执行标记了www并且也标记aaa的用例
>pytest -m "www and aaa" -s
4.执行没有标记www的用例
>pytest -m "not www" -s
5.执行标记了www或aaa的用例
>pytest -m "www or aaa" -s
pytest 执行用例的时候,我们希望对用例的运行时间断言,当用例执行时长大于预期标记此用例失败。
@pytest.mark.runtime(1)
运行时长单位是秒
此插件已打包上传到pypi https://pypi.org/project/pytest-runtime-yoyo/
环境准备
pip install pytest-yaml-yoyo
此功能在v1.3.1 版本上实现
基本示例 test_demo.py
import pytest
import time
def test_a1():
time.sleep(2)
@pytest.mark.runtime(1)
def test_a2():
time.sleep(2)
运行结果
======================== short test summary info =====================
FAILED test_demo.py::test_a2
======================== 1 failed, 1 passed in 4.18s ===============
在yaml 中添加 runtime
config:
name: yy
test_a1:
name: a11
mark: runtime(1)
sleep: 2
print: "xx111"
test_a2:
name: a22
sleep: 2
print: "xx22"
也可以在config 中添加runtime 对整个yaml 中的用例标记
config:
name: yy
mark: runtime(1)
test_a1:
name: a11
sleep: 2
print: "xx111"
test_a2:
name: a22
sleep: 2
print: "xx22"
如果config 中和用例中都有runtime ,那么用例的runtime优先级>config 中的runtime
对全部用例设置 runtime 标记,可以在 pytest.ini
中设置全局配置
[pytest]
runtime = 3
也可以在执行 pytest 命令的时候带上命令行参数--runtime
pytest --runtime=3
优先级是: 命令行参数 > pytest.ini 配置 全局配置只针对yaml 中config,测试用例没标记 runtime 的用例生效。 如果yaml 中config,测试用例有标记 runtime,那么优先级是大于全局配置的。
export 导出功能, config 和 case 中都能添加。 简单来说,就是test_a.yml 执行完成后,提取了变量x, 在后面的test_b.yml 和 test_c.yml 中可以直接引用变量x了。
在单个测试yaml 用例文件中,可以支持写多个用例,并且extract 提取的变量,在整个yaml文件中都可以直接引用。
test_ext3.yml
# 作者-上海悠悠 wx:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
config:
name: 提取变量
variables:
name: msg
test_ext1:
name: 提取email
print: ${name}
extract:
email: 123@qq.com
test_ext2:
name: 引用extract 变量
print: ${email}
test_ext3:
-
name: 步骤1
print: ${email}
-
name: 步骤2
print: ${email}
使用pytest test_ext3.yml
命令运行用例
(venv) D:\demo>pytest test_ext3.yml
=========================== test session starts ==================================================
platform win32 -- Python 3.8.5, pytest-7.3.0, pluggy-1.0.0
rootdir: D:\demo\untitled6_demo_test
configfile: pytest.ini
plugins: allure-pytest-2.13.1, Faker-18.4.0, yaml-yoyo-1.2.3
collecting ...
----------------- live log collection ---------------------------
2023-05-14 10:15:22 [INFO]: --------[{'name': '提取email', 'print': '${name}', 'extract': {'email': '123@qq.com'}}]
2023-05-14 10:15:22 [INFO]: --------[{'name': '引用extract 变量', 'print': '${email}'}]
2023-05-14 10:15:22 [INFO]: --------[{'name': '步骤1', 'print': '${email}'}, {'name': '步骤2', 'print': '${email}'}]
collected 3 items
test_extract/test_ext3.yml::test_ext1
------------------- live log call ------------------------
2023-05-14 10:15:22 [INFO]: 执行文件-> test_ext3.yml
2023-05-14 10:15:22 [INFO]: base_url-> http://124.70.221.221:8201
2023-05-14 10:15:22 [INFO]: config variables-> {'name': 'msg'}
2023-05-14 10:15:22 [INFO]: 运行用例-> test_ext1
2023-05-14 10:15:22 [INFO]: 取值表达式 name
2023-05-14 10:15:22 [INFO]: 取值结果:msg, <class 'str'>
2023-05-14 10:15:22 [INFO]: extract 提取变量-> {'email': '123@qq.com'}
2023-05-14 10:15:22 [INFO]: validate 校验内容-> []
2023-05-14 10:15:22 [INFO]: export 导出全局变量:{}
PASSED [ 33%]
test_extract/test_ext3.yml::test_ext2
------------------- live log call -------------------
2023-05-14 10:15:22 [INFO]: 执行文件-> test_ext3.yml
2023-05-14 10:15:22 [INFO]: base_url-> http://124.70.221.221:8201
2023-05-14 10:15:22 [INFO]: config variables-> {'name': 'msg', 'email': '123@qq.com'}
2023-05-14 10:15:22 [INFO]: 运行用例-> test_ext2
2023-05-14 10:15:22 [INFO]: 取值表达式 email
2023-05-14 10:15:22 [INFO]: 取值结果:123@qq.com, <class 'str'>
2023-05-14 10:15:22 [INFO]: validate 校验内容-> []
2023-05-14 10:15:22 [INFO]: export 导出全局变量:{}
PASSED [ 66%]
test_extract/test_ext3.yml::test_ext3
------------- live log call ------------
2023-05-14 10:15:22 [INFO]: 执行文件-> test_ext3.yml
2023-05-14 10:15:22 [INFO]: base_url-> http://124.70.221.221:8201
2023-05-14 10:15:22 [INFO]: config variables-> {'name': 'msg', 'email': '123@qq.com'}
2023-05-14 10:15:22 [INFO]: 运行用例-> test_ext3
2023-05-14 10:15:22 [INFO]: 取值表达式 email
2023-05-14 10:15:22 [INFO]: 取值结果:123@qq.com, <class 'str'>
2023-05-14 10:15:22 [INFO]: validate 校验内容-> []
2023-05-14 10:15:22 [INFO]: 取值表达式 email
2023-05-14 10:15:22 [INFO]: 取值结果:123@qq.com, <class 'str'>
2023-05-14 10:15:22 [INFO]: validate 校验内容-> []
2023-05-14 10:15:22 [INFO]: export 导出全局变量:{}
PASSED [100%]
=============== 3 passed in 0.38s ==
test_ext3.yml 用例中提取的email 变量作用范围仅在test_ext3.yml 中使用有效,无法跨yaml 文件引用。
如果想后面的用例,继续使用提取的email 变量, 需使用export 关键字,提升变量的级别为session会话级别,也就是真正的全局变量。
export
关键字可以写到config 也可以写到用例中,格式必须是list 类型。
test_extract/test_ext3.yml
config:
name: 提取变量
variables:
name: msg
export:
- email
test_ext1:
name: 提取email
print: ${name}
extract:
email: 123@qq.com
在接下来的test_ext4.yml 和其它用例可以直接引用 ${export}
test_extract/test_ext4.yml
# 作者-上海悠悠 wx:283340479
# blog地址 https://www.cnblogs.com/yoyoketang
config:
name: 提取变量
test_ext5:
-
name: 步骤1
print: ${email}
-
name: 步骤2
print: ${email}
test_ext6:
name: 6666
print: ${email}
前提条件是 test_ext3.yml 用例要先执行,pytest 在执行的时候按用例名称顺序执行, 可以放到test_extract 同一个文件夹下一起执行
pytest test_extract
执行后部分log日志
test_extract/test_ext4.yml::test_ext6
----------------------------------------------------- live log call -----------------------------------------------------
2023-05-14 10:22:07 [INFO]: 执行文件-> test_ext4.yml
2023-05-14 10:22:07 [INFO]: base_url-> http://124.70.221.221:8201
2023-05-14 10:22:07 [INFO]: config variables-> {}
2023-05-14 10:22:07 [INFO]: 运行用例-> test_ext6
2023-05-14 10:22:07 [INFO]: 取值表达式 email
2023-05-14 10:22:07 [INFO]: 取值结果:123@qq.com, <class 'str'>
2023-05-14 10:22:07 [INFO]: validate 校验内容-> []
2023-05-14 10:22:07 [INFO]: export 导出全局变量:{'email': '123@qq.com'}
PASSED
export 关键字也可以在用例中使用,跟config 中使用效果引用,如果2个地方都有,会自动合并
test_extract/test_ext3.yml
config:
name: 提取变量
variables:
name: msg
test_ext1:
name: 提取email
print: ${name}
extract:
email: 123@qq.com
export:
- email
在整个用例中有 config 配置文件中设置的env 环境变量,export 导出的session 变量,yaml 文件中的config 模块变量,用例中的variables 局部变量。 整体优先级是:用例中的variables 局部变量 > extract 提取变量 > yaml 文件中的config 模块变量 > export 导出的session 变量 > 配置文件中设置的env 环境变量 test_extract/test_ext5.yml
config:
name: 提取变量
test_ext7:
name: 6666
variables:
email: yoyo@qq.com
print: ${email}
test_ext8:
name: 6666
print: ${email}
pytest test_extract
执行用例
test_extract/test_ext5.yml::test_ext7
----------------------------------------------------- live log call -----------------------------------------------------
2023-05-14 10:34:16 [INFO]: 执行文件-> test_ext5.yml
2023-05-14 10:34:16 [INFO]: base_url-> http://124.70.221.221:8201
2023-05-14 10:34:16 [INFO]: config variables-> {}
2023-05-14 10:34:16 [INFO]: 运行用例-> test_ext7
2023-05-14 10:34:16 [INFO]: 取值表达式 email
2023-05-14 10:34:16 [INFO]: 取值结果:yoyo@qq.com, <class 'str'>
2023-05-14 10:34:16 [INFO]: validate 校验内容-> []
2023-05-14 10:34:16 [INFO]: export 导出全局变量:{'email': '123@qq.com'}
PASSED [ 54%]
test_extract/test_ext5.yml::test_ext8
----------------------------------------------------- live log call -----------------------------------------------------
2023-05-14 10:34:16 [INFO]: 执行文件-> test_ext5.yml
2023-05-14 10:34:16 [INFO]: base_url-> http://124.70.221.221:8201
2023-05-14 10:34:16 [INFO]: config variables-> {}
2023-05-14 10:34:16 [INFO]: 运行用例-> test_ext8
2023-05-14 10:34:16 [INFO]: 取值表达式 email
2023-05-14 10:34:16 [INFO]: 取值结果:123@qq.com, <class 'str'>
2023-05-14 10:34:16 [INFO]: validate 校验内容-> []
2023-05-14 10:34:16 [INFO]: export 导出全局变量:{'email': '123@qq.com'}
PASSED
可以看到 test_ext7 用例引用局部变量值yoyo@qq.com
, test_ext8 引用export 导出的 session 会话变量值123@qq.com
飞书机器人配置详细文档参考:https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN
第一步:邀请自定义机器人入群
进入你的目标群组,打开会话设置,找到群机器人,并点击添加机器人,选择自定义机器人加入群聊。
第二步: 设置机器人名称和描述 为你的机器人输入一个合适的名字和描述,也可以为机器人设置一个合适的头像,然后点击下一步。
第三步:复制 webhook 地址
webhook地址复制出来:https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxxxx
hook/
后面的一串 xxxxxxxxxxxxxxxxx
就是我们要的 token
在项目根目录(pytest.ini 同级文件)创建一个config.py 文件
# 作者-上海悠悠 wx:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
class Config:
"""多套环境的公共配置"""
version = "v1.0"
# 飞书机器人通知
FEI_SHU = {
"token": "*****************", # 必须
# "text": "- 查看报告:[allure报告地址](https://www.cnblogs.com/yoyoketang/)" # 非必须
}
class TestConfig(Config):
"""测试环境"""
# ....
class UatConfig(Config):
"""联调环境"""
# ....
# 环境关系映射,方便切换多环境配置
env = {
"test": TestConfig,
"uat": UatConfig
}
在 pytest.ini 中配置运行环境
[pytest]
env = test
通过pytest 命令行运行用例
pytest
运行完成后,会根据你配置的token内容,触发飞书通知
# 作者-上海悠悠 wx:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
# 飞书机器人通知
FEI_SHU = {
"token": "*****************", # 必须
# "text": "- 查看报告:[allure报告地址](https://www.cnblogs.com/yoyoketang/)" # 非必须
}
用例成功,背景显示绿色
用例失败,背景显示红色
企业机器人相关接口可以看官方文档https://developer.work.weixin.qq.com/document/path/91770 创建群聊机器人
获取到webhook访问地址https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=*********************
key= 后面的这串就是我们要的token
在运行环境配置中加一个WE_COM的配置即可
# 配置企业微信群通知
WE_COM = {
"token": "******复制前面得到的token*********",
}
配置 WE_COM
class Config:
"""多套环境的公共配置"""
version = "v1.0"
# 配置企业微信群通知
WE_COM = {
"token": "******复制前面得到的token*********",
}
class TestConfig(Config):
"""测试环境"""
BASE_URL = 'http://192.168.1.1:8000'
class UatConfig(Config):
"""联调环境"""
BASE_URL = 'http://192.168.1.3:8080'
# 环境关系映射,方便切换多环境配置
env = {
"test": TestConfig,
"uat": UatConfig
}
在 pytest.ini 中配置运行环境
[pytest]
env = test
执行用例就会自动触发企业微信机器人
针对有同学提到上个接口返回一个id值,下个接口引用变量的时候需要根据这个值做一些运算,比如在引用的结果加1. 本篇介绍下模板过滤器的相关使用.
什么是 jinja2 模板过滤器?
通过在 Jinja2 模板中嵌入变量,可以输出变量的值。但是,在很多情况下,不仅仅只是需要输出变量的值,还需要修改变量的显示,对变量进行格式化、运算等。 为了方便对变量进行处理,Jinja2 提供了过滤器,Jinja2 过滤器是一个函数,该函数对输入变量进行变换处理后,返回一个值作为 Jinja2 模板的输出。
在 yaml 用例文件中引用变量,使用 Jinja2 中过滤器有如下用法:
示例1:过滤器不带任何参数
${ var | filter }
例如:过滤器 upper 将输入变量 var 转换为大写, ${"hello" | upper }
的输出为 "HELLO"。
示例2:过滤器带参数的情况
${ var | filter(arg) }
例如:过滤器 replace (source, target) 将输入变量 var 中的字符串 source 替换为字符串 target,
${"hello world" | replace ("hello", "yoyo") }
的输出为 "hello yoyo"。
示例3:过滤器可以组合使用
${ var | filterA | filterB }
过滤器可以组合使用,${var | filterA | filterB}
的处理过程如下:
例如:${"abc" | upper | reverse }
的输出为 "CBA"。
过滤器名称 | 语法使用 | 实现功能 |
---|---|---|
capitalize | ${ 'yoyo' | capitalize } |
首字母转大写: Yoyo |
title | ${ 'hello world' | title } |
单词首字母大写 Hello World |
lower | ${ 'HELLO' | lower } |
转小写: hello |
upper | ${ 'hello' | upper } |
转大写: HELLO |
revsere | ${ 'abc' | reverse } |
反转: cba |
format | ${ 'my name %s, %d years.' | format('yoyo', 20) } |
format 格式化字符:my name yoyo, 20 years. |
first | ${ ['hello', 'world', 'yoyo'] | first} |
取出第一个:hello |
last | ${ ['hello', 'world', 'yoyo'] | last} |
取出最后一个:yoyo |
length | ${ ['hello', 'world', 'yoyo'] | length} |
获取列表 list 长度: 3 |
sum | ${ [1, 2, 3] |sum } |
list 求和: 6 |
sort | ${ [1, 3, 2] | sort } |
list 排序:123 |
sort | { [1, 3, 2] | sort(reverse = True) } |
list 倒序:321 |
join | ['hello', 'world', 'yoyo'] | join('_') |
字符拼接:hello_world_yoyo |
default | ${ gender | default('male') } |
如果 gender 变量未定义使用默认值:male |
add | ${20 | add(1)} |
(这个是我添加的一个add 方法)变量值加1: 21 |
上面的过滤器方法除了add是我自定义的一个内置方法,其它都是jinja2模板引擎自带的过滤器方法。
在yaml 用例中使用过滤器语法 test_a.yml
# 作者-上海悠悠 wx:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
config:
name: xx
variables:
age: 20
x: 22
y: "hell0"
testx1:
name: "xx"
print: '${age | add(3)}'
testx2:
name: "xx"
print: '${y | length}'
使用pytest 运行用例
pytest test_a.yml
运行结果:
test_a.yml::testx1
------------------------- live log call ------------------------
2023-05-23 10:16:48 [INFO]: 执行文件-> test_a.yml
2023-05-23 10:16:48 [INFO]: base_url->
2023-05-23 10:16:48 [INFO]: config variables-> {'age': 20, 'x': 22, 'y': 'hell0'}
2023-05-23 10:16:48 [INFO]: 运行用例-> testx1
2023-05-23 10:16:48 [INFO]: 取值表达式 age | add(3)
2023-05-23 10:16:48 [INFO]: 取值结果:23, <class 'int'>
2023-05-23 10:16:48 [INFO]: validate 校验内容-> []
2023-05-23 10:16:48 [INFO]: export 导出全局变量:{}
PASSED [ 50%]
test_a.yml::testx2
---------------------- live log call ------------------------------
2023-05-23 10:16:48 [INFO]: 执行文件-> test_a.yml
2023-05-23 10:16:48 [INFO]: base_url->
2023-05-23 10:16:48 [INFO]: config variables-> {'age': 20, 'x': 22, 'y': 'hell0'}
2023-05-23 10:16:48 [INFO]: 运行用例-> testx2
2023-05-23 10:16:48 [INFO]: 取值表达式 y | length
2023-05-23 10:16:48 [INFO]: 取值结果: 5, <class 'str'>
2023-05-23 10:16:48 [INFO]: validate 校验内容-> []
2023-05-23 10:16:48 [INFO]: export 导出全局变量:{}
PASSED [100%]
自定义过滤器语法跟自定义函数有点类似,也是在项目根路径下conftest.py 文件上注册过滤器
conftest.py
from pytest_yaml_yoyo.render_template_obj import env_filter
# 作者-上海悠悠 wx:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
def fun_a(value):
"""不带参数"""
return f"{value} 不带参数: {value}"
def fun_b(value, x):
"""带一个参数x"""
return f"{value} 带参数x: {x}"
# 注册过滤器
env_filter.filters['fun_a'] = fun_a
env_filter.filters['fun_b'] = fun_b
yaml 中用例使用 test_b.yml
# 作者-上海悠悠 wx:283340479
# blog地址 https://www.cnblogs.com/yoyoketang/
config:
name: 自定义过滤器的使用
variables:
name: yoyo
testa1:
name: "不带参数"
print: '${name | fun_a}'
testa2:
name: "带参数"
print: '${name | fun_b("bb")}'
运行结果
test_b.yml::testa1
------------------- live log call -----------------------------
2023-05-23 10:38:48 [INFO]: 执行文件-> test_b.yml
2023-05-23 10:38:48 [INFO]: base_url->
2023-05-23 10:38:48 [INFO]: config variables-> {'name': 'yoyo'}
2023-05-23 10:38:48 [INFO]: 运行用例-> testa1
2023-05-23 10:38:48 [INFO]: 取值表达式 name | fun_a
2023-05-23 10:38:48 [INFO]: 取值结果: yoyo 不带参数: yoyo, <class 'str'>
2023-05-23 10:38:48 [INFO]: validate 校验内容-> []
2023-05-23 10:38:48 [INFO]: export 导出全局变量:{}
PASSED [ 75%]
test_b.yml::testa2
------------------------- live log call -------------------
2023-05-23 10:38:48 [INFO]: 执行文件-> test_b.yml
2023-05-23 10:38:48 [INFO]: base_url->
2023-05-23 10:38:48 [INFO]: config variables-> {'name': 'yoyo'}
2023-05-23 10:38:48 [INFO]: 运行用例-> testa2
2023-05-23 10:38:48 [INFO]: 取值表达式 name | fun_b("bb")
2023-05-23 10:38:48 [INFO]: 取值结果: yoyo 带参数x: bb, <class 'str'>
2023-05-23 10:38:48 [INFO]: validate 校验内容-> []
2023-05-23 10:38:48 [INFO]: export 导出全局变量:{}
PASSED [100%]
在 yaml 用例中提取返回结果,可以支持以下三种表达式
以上三种表达式可以满足 99% 的测试场景需求了,但是有些特殊的需求通过表达式无法取到,为了满足另外1%的需求,可以自定义函数取值。 此功能在v1.3.6版本实现
有个小伙伴给我提了个需求:如果返回的结果中有某个值就断言,没有就不断言
示例:如下返回结果,当data中name的值为"yoyo"的时候,断言它的邮箱值"283340479@qq.com",如果结果中没有name的值为"yoyo"就不断言
res = {
"code": 0,
"msg": "成功success!",
"data": [
{
"age": 20,
"create_time": "2019-09-15",
"id": 1,
"mail": "283340479@qq.com",
"name": "yoyo",
"sex": "M"
},
{
"age": 21,
"create_time": "2019-09-16",
"id": 2,
"mail": "123445@qq.com",
"name": "yoyo111",
"sex": "M"
}
]
}
先自定义函数取值,传一个 response (接口返回对象)
conftest.py 内容如下:
from pytest_yaml_yoyo import my_builtins
def fun_x(response):
res = response.json()
for item in res['data']:
if item.get("name") == "yoyo":
return item.get("mail") == "283340479@qq.com"
return True
my_builtins.fun_x = fun_x
yaml 用例中内容, 校验地方可以引用函数${fun_x(response)}
, response 参数是接口返回对象。
test_rep.yml
test_rep:
name: z
request:
url: /api/test/demo
method: get
validate:
- eq: ["hello", "hello"]
- eq: ["${fun_x(response)}", true]
执行用例
pytest test_rep.yml
运行结果:
2023-06-26 22:45:08 [INFO]: 取值表达式: fun_x(response), 取值结果:True <class 'bool'>
2023-06-26 22:45:08 [INFO]: validate 校验内容-> [{'eq': ['hello', 'hello']}, {'eq': [True, True]}]
2023-06-26 22:45:08 [INFO]: validate 校验结果-> eq: [hello, hello]
2023-06-26 22:45:08 [INFO]: validate 校验结果-> eq: [True, True]
2023-06-26 22:45:08 [INFO]: export 导出全局变量:{}
v1.3.8 版本对 parameters 参数化格式重新做了定义,支持笛卡尔积了。当然以前旧版本的格式还是继续兼容。
新版本对 parameters 参数化重新做了定义,简化了步骤,更加清晰简洁.
1.只有一个变量需要参数化的情况
test_p1.yml
config:
parameters:
x: ["a", "b", "c"]
test_a:
print: "输出-${x}"
运行结果
test_p1.yml 输出-a
.输出-b
.输出-c
.
2.有2个变量需要参数化的情况,变量中间用逗号隔开(或者-) test_p2.yml
config:
parameters:
x,y: [["a", "b"], ['c', 'd']]
test_b:
print: "输出-${x}-${y}"
也可以实现横线隔开
config:
parameters:
x-y: [["a", "b"], ['c', 'd']]
运行结果
test_p2.yml 输出-a-b
.输出-c-d
.
对2个变量同时参数化,生成笛卡尔积的情况
x 变量只有一个值,可以写成x: "a"
, 也可以写成x: ["a"]
test_p3.yml
config:
parameters:
x: "a"
y: ["hello", "world"]
test_r:
name: 11
print: "组合-${x}-${y}"
x 变量也可以有多个值 test_p4.yml
config:
parameters:
x: ["a", 'b']
y: ["hello", "world"]
test_r:
name: 11
print: "组合-${x}-${y}"
运行结果
test_p4.yml 组合-a-hello
.组合-a-world
.组合-b-hello
.组合-b-world
.
支持模块级别和用例级别参数化
用例级别参数化 test_p5.yml
config:
name: demo
test_1:
name: 用例1
parameters:
user: [hello, world]
print: "case-${user}"
运行结果
test_p5.yml case-hello
.case-world
.
config 中 parameters 参数化,作用域是整个模块级别, test_p6.yml 文件示例
config:
parameters:
x: ["a", 'b']
y: ["hello", "world"]
test_a:
print: "组合-${x}-${y}"
test_b:
print: "组合-${x}-${y}"
运行结果
test_p6.yml 组合-a-hello
.组合-a-hello
.组合-a-world
.组合-a-world
.组合-b-hello
.组合-b-hello
.组合-b-world
.组合-b-world
.
有小伙伴提到能不能只收集用例失败的情况下日志和错误信息,每个用例都收集日志内容太多了,没法直观的看错误的用例信息。 v1.3.9 版本实现此功能,在原有的功能上新增一个log日志文件,仅收集用例错误的日志。
用例执行完会默认生成2个日志文件
使用示例
config:
name: demo
test_1:
name: 用例1
print: "111111"
test_2:
name: 用例2
print: "2222"
validate:
- eq: [200, 201]
用例执行后根据当前时间生成一个20230707_201045_error.log 日志文件
************************* a1/test_a2.yml::test_2 *************************
测试结果 outcome:failed
用例耗时 duration:0.003574687999999826
异常 exception:<ExceptionInfo AssertionError("200-><class 'int'> == 201-><class 'int'>\nassert 200 == 201") tblen=4>
exception详细日志:request = <FixtureRequest for <Function test_2>>
requests_session = <pytest_yaml_yoyo.http_session.HttpSession object at 0x0000022D4EF43D00>
> ???
test_a2.py:2:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv\lib\site-packages\pytest_yaml_yoyo\runner.py:222: in execute_yaml_case
self.validate_response(response, validate_value)
venv\lib\site-packages\pytest_yaml_yoyo\runner.py:559: in validate_response
validate.equals(actual_value, expect_value)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
check_value = 200, expect_value = 201
def equals(check_value, expect_value):
check_value = None if check_value == 'None' else check_value
expect_value = None if expect_value == 'None' else expect_value
> assert check_value == expect_value, f'{check_value}->{type(check_value)} == {expect_value}->{type(expect_value)}'
E AssertionError: 200-><class 'int'> == 201-><class 'int'>
E assert 200 == 201
venv\lib\site-packages\pytest_yaml_yoyo\validate.py:10: AssertionError
Captured stdout call2222
Captured log call[32mINFO [0m pytest_yaml_yoyo.log:runner.py:128 执行文件-> test_a2.yml
[32mINFO [0m pytest_yaml_yoyo.log:runner.py:129 base_url-> http://124.70.221.221:8201
[32mINFO [0m pytest_yaml_yoyo.log:runner.py:130 config variables-> {}
[32mINFO [0m pytest_yaml_yoyo.log:runner.py:132 运行用例-> test_2
[32mINFO [0m pytest_yaml_yoyo.log:runner.py:221 validate 校验内容-> [{'eq': [200, 201]}]
[32mINFO [0m pytest_yaml_yoyo.log:runner.py:557 validate 校验结果-> eq: [200, 201]
根据用例节点a1/test_a2.yml::test_2
详细记录用例报错的内容和用例运行的日志.
v1.4.0 版本支持allure报告自定义内容
用例中可以通过dynamic 添加以下内容
在test_a.yml 用例中示例
config:
name: 用例描述
test_a1:
name: 用例a1
allure:
feature: demo模块
story: 用例场景
title: 用例详细描述a1
severity: critical
test_a2:
name: 用例a2
allure:
feature: demo模块
story: 用例场景
title: 用例详细描述a2
severity: critical
命令行执行用例,并启动allure服务查看报告
> pytest test_a.yml --alluredir ./report
> allure serve ./report
在test_b.yml 用例中有多个步骤:
test_b.yml 示例
config:
name: 用例描述
test_b2:
-
allure:
title: 用例有多个步骤情况
-
name: 步骤1
print: hello
-
name: 步骤2
print: world
同一个 yaml 文件中,有多个用例都需加相同的 allure 内容,可以在 config 中定义公共的allure内容 test_c.yml 内容
config:
name: 用例描述
allure:
feature: config是公共的
test_c1:
name: ss1
print: hello world
test_c2:
name: ss1
allure:
title: xxxxxxxxx
print: hello world
test_c3:
name: ss1
allure:
title: yyyyyyyy
print: hello world
v1.4.1 新增 redis 数据库配置
环境准备
pip instal redis
python 操作redis 基本代码
import redis
# 连上redis
r = redis.StrictRedis(
host='服务器ip', port=6379, decode_responses=True
)
# 设置一个键值对
r.set('name', 'yoyo')
# 获取结果
print(r.get('name')) # yoyo
config 配置中配置 REDIS
class Config:
"""每个环境都有一样的公共配置"""
version = "v1.0"
class TestConfig(Config):
"""测试环境"""
BASE_URL = 'http://127.0.0.1:8000'
# ....
REDIS = {
"host": '你的redis地址',
"port": 6379,
"decode_responses": True
}
class UatConfig(Config):
"""联调环境"""
BASE_URL = 'http://127.0.0.1:88'
USERNAME = 'test_uat'
PASSWORD = '123456'
# 环境关系映射,方便切换多环境配置
env = {
"test": TestConfig,
"uat": UatConfig
}
yaml 用例中使用 'redis'对象,调用对应方法如:'redis.get('x')'
config:
name: x
test_red1:
name: 测试redis
variables:
x: ${redis.get("name")}
validate:
- eq: ['${x}', yoyo]
v1.4.2 版本支持 websocket 协议
环境准备
pip3 install websocket-client
pip3 install websockets
基本代码示例
from websocket import create_connection
import json
url = 'ws://localhost:8081/ws'
ws = create_connection(url, timeout=10) # 创建链接
print(ws.getstatus()) # 状态码
ws.send('hello world') # 发送文本消息
print(ws.recv()) # 接收返回内容
# 发送json消息
body = {
"name": "yoyo",
"email": "123@qq.com"
}
ws.send(json.dumps(body))
print(ws.getstatus())
print(ws.recv())
ws.shutdown() # 关闭连接
运行结果
101
接受到的消息是: hello world
101
接受到的消息是: {"name": "yoyo", "email": "123@qq.com"}
ws关键字相当于create_connection(url, timeout=10)
创建连接
send 关键字相当于 ws.send('hello')
发送文本消息`
test_x1:
ws:
url: ws://localhost:8081/ws
timeout: 10
send: hello
validate:
- eq: [status, 101]
- eq: [text, '接受到的消息是: hello']
运行结果
collected 1 item
a_ws/test_ws.yml::test_x1
---------------------------------------------------- live log call -----------------------------------------------------
2023-07-17 19:44:52 [INFO]: 执行文件-> test_ws.yml
2023-07-17 19:44:52 [INFO]: base_url-> http://124.70.221.221:8201
2023-07-17 19:44:52 [INFO]: config variables-> {}
2023-07-17 19:44:52 [INFO]: 运行用例-> test_x1
2023-07-17 19:44:53 [INFO]: 创建 websocket 链接: ws://localhost:8081/ws
2023-07-17 19:44:53 [INFO]: websocket send: hello
2023-07-17 19:44:53 [INFO]: websocket recv: 接受到的消息是: hello
2023-07-17 19:44:53 [INFO]: validate 校验内容-> [{'eq': ['status', 101]}, {'eq': ['text', '接受到的消息是: hello']}]
2023-07-17 19:44:53 [INFO]: validate 校验结果-> eq: [101, 101]
2023-07-17 19:44:53 [INFO]: validate 校验结果-> eq: [接受到的消息是: hello, 接受到的消息是: hello]
PASSED
websocket 一次连接可以发送多个请求,所以可以在一个用例中有多个send请求
test_x2:
-
ws:
url: ws://localhost:8081/ws
timeout: 10
-
send: hello
validate:
- eq: [status, 101]
- eq: [text, '接受到的消息是: hello']
-
send:
name: yoyo
email: w22@qq.com
validate:
- eq: [status, 101]
- eq: [text, '接受到的消息是: {"name": "yoyo", "email": "w22@qq.com"}']
一个yaml 文件中可以有多个连接
test_x1:
-
ws:
url: ws://localhost:8081/ws
timeout: 10
-
send: hello
validate:
- eq: [status, 101]
- eq: [text, '接受到的消息是: hello']
- eq: [body, '接受到的消息是: hello']
test_x2:
ws:
url: ws://localhost:8081/json
timeout: 10
send:
name: xx
email: w22@qq.com
validate:
- eq: [status, 101]
- eq: [$.name, xx]
多个url地址公共环境地址 base_url 也可以写的config中
config:
base_url: ws://localhost:8081
test_x1:
-
ws:
url: /ws
timeout: 10
-
send: hello
validate:
- eq: [status, 101]
- eq: [text, '接受到的消息是: hello']
- eq: [body, '接受到的消息是: hello']
test_x2:
ws:
url: /json
timeout: 10
send:
name: xx
email: w22@qq.com
validate:
- eq: [status, 101]
- eq: [$.name, xx]
执行结果
collected 2 items
a_ws/test_ws1.yml::test_x1
-------------------------------live log call ---------------------------------------
2023-07-17 19:50:07 [INFO]: 执行文件-> test_ws1.yml
2023-07-17 19:50:07 [INFO]: base_url-> ws://localhost:8081
2023-07-17 19:50:07 [INFO]: config variables-> {}
2023-07-17 19:50:08 [INFO]: 创建 websocket 链接: ws://localhost:8081/ws
2023-07-17 19:50:08 [INFO]: websocket send: hello
2023-07-17 19:50:08 [INFO]: websocket recv: 接受到的消息是: hello
2023-07-17 19:50:08 [INFO]: validate 校验内容-> [{'eq': ['status', 101]}, {'eq': ['text', '接受到的消息是: hello']}, {'eq
': ['body', '接受到的消息是: hello']}]
2023-07-17 19:50:08 [INFO]: validate 校验结果-> eq: [101, 101]
2023-07-17 19:50:08 [INFO]: validate 校验结果-> eq: [接受到的消息是: hello, 接受到的消息是: hello]
2023-07-17 19:50:08 [INFO]: validate 校验结果-> eq: [接受到的消息是: hello, 接受到的消息是: hello]
PASSED [ 50%]
----------------- live log call -------------------------------------
2023-07-17 19:50:08 [INFO]: 执行文件-> test_ws1.yml
2023-07-17 19:50:08 [INFO]: base_url-> ws://localhost:8081
2023-07-17 19:50:08 [INFO]: config variables-> {}
2023-07-17 19:50:08 [INFO]: 运行用例-> test_x2
2023-07-17 19:50:09 [INFO]: 创建 websocket 链接: ws://localhost:8081/json
2023-07-17 19:50:09 [INFO]: websocket send: {'name': 'xx', 'email': 'w22@qq.com'}
2023-07-17 19:50:09 [INFO]: websocket recv: {"name": "xx", "email": "w22@qq.com", "code": 0, "time": 1689594609.5537014}
2023-07-17 19:50:09 [INFO]: validate 校验内容-> [{'eq': ['status', 101]}, {'eq': ['$.name', 'xx']}]
2023-07-17 19:50:09 [INFO]: validate 校验结果-> eq: [101, 101]
2023-07-17 19:50:09 [INFO]: validate 校验结果-> eq: [xx, xx]
PASSED [100%]
================== 2 passed in 2.47s ============================
v1.0.0 发布的第一个版本(已删除)
v1.0.1 发布时间 2022.11.23 可以安装的第一个版本
v1.0.2 发布时间 2022.11.24
详细功能参阅 extract 关键字文档
v1.0.3 发布时间 2022.11.28
详细功能参阅 parameters 参数化 关键字文档
v1.0.4 发布时间 2022.11.30
hooks 钩子功能实现
详细功能参阅 hooks 钩子 关键字文档
v1.0.5 发布时间 2022.12.05
用例分层机制
v1.0.6 发布时间 2022.12.06
一个yaml 中写多个用例,用例步骤可以不是list 类型
v1.0.7 发布时间 2022.12.08
新增日志
v1.0.8 发布时间 2022.12.09
结合 allure 生成报告
v1.0.9 发布时间 2022.12.09
全局 base_url 配置
base_url
配置--base-url
参数v1.1.0 发布时间 2022.12.13
多环境配置切换
env
配置, 命令行新增 --env
参数v1.1.1 发布时间 2022.12.14
钉钉机器人通知测试结果
v1.1.2 发布时间 2022.12.16
内置方法提供
v1.1.3 发布时间 2022.12.17
v1.1.4 发布时间 2023.2.13
新增3个关键字
v1.1.5 发布时间 2023.2.16
支持 2 中方式生成 yaml 用例
v1.1.6 发布时间 2023.2.17
支持2种方式实现
--proxies-ip=代理ip:端口
--proxies-ip=代理ip:端口
v1.1.8 发布时间 2023.3.17
int 转 str 类型
v1.1.9 发布时间 2023-03-21
做了以下优化
v1.2.0 发布时间 2023-05-08
优化以下问题
v1.2.1 发布时间 2023-05-20
优化以下问题
txt/csv/json/yaml
${fun("hello ${key}")}
to_json()
函数,字典转 jsonv1.2.3 版本 1.解决用例中,hooks单独写request 报错问题 ---已发布的1.2.0解决 2.内置to_json() 函数,字典转 json 4.export 导出功能, config 和 case 中都能添加
v1.2.4 版本 1.新增飞书机器人报告 2.统计报告添加skip 统计
v1.2.5 版本 1.函数的参数可以引用变量了,如: ${fun("hello ${key}")} 2.兼容python3.8, python3.9, python3.10版本
v1.2.6 1.解决与 pytest-base-url 插件不兼容问题 2.解决len_eq 断言 list 长度问题 3.模板过滤器 filter 支持
v1.2.7 1.解决请求钩子函数中传环境配置问题 2.新增内置fixture environ 返回当前运行环境对象
v1.2.8 1.解决用例全部 skip 报错问题 2.解决文件上传参数全部传 files 不生效问题 3.数据库配置支持 DB_INFO 参数传字典类型 4.jmespath 表达式支持length 等函数的提取
v1.2.9 发布
1.优化参数化路径读取,以项目根路径拼接路径 2.优化len_eq 3.变量取值优化
v1.3.0
1.mark 标记实现,config 和用例中都能加 mark
v1.3.1
1.用例超时功能需安装 pytest-runtime-yoyo 插件
2.mark: runtime(1)
标记用例运行超时,单位是秒
v1.3.2 1.企业微信机器人通知
v1.3.3
1.添加--start-project
创建项目demo
v1.3.4 1.录制功能
v1.3.5 1.录制功能,支持 multipart/form-data 2.优化录制 yaml 格式用例
v1.3.6 1.export 支持局部变量提升 2.validate 和 extract 支持自定义函数提取
v1.3.7 1.parameters 参数化支持模块级别
v1.3.8 1.parameters 参数化格式重新定义,支持笛卡尔积
v1.3.9 1.log 日志新增只收集用例失败日志
v1.4.0 allure 报告动态添加
v1.4.1 支持redis 配置 支持配置多数据库
v1.4.2 支持 websocket 协议
作者-上海悠悠 微信/QQ交流:283340479 blog地址 https://www.cnblogs.com/yoyoketang/
完整视频教程地址https://study.163.com/course/courseMain.htm?courseId=1213419817&share=2&shareId=480000002230338 (视频收费:包含 pytest + yaml 框架开发教学和使用教学, 买课程的学员可以用新版本更多强大功能,咨询wx:283340479)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。