# pytest-yaml-yoyo **Repository Path**: e-raticwatcher/pytest-yaml-yoyo ## Basic Information - **Project Name**: pytest-yaml-yoyo - **Description**: Python + pytest + yaml + allure + log + 钉钉/飞书、企微群通知 +mysql/redis+ swagger.json 自动生成 yaml 接口用例+录制yaml用例+mock。本框架优势是pip install 安装插件,仅需一个yaml 文件即可运行用例。 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 43 - **Created**: 2023-12-13 - **Last Updated**: 2023-12-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 1 环境与准备 ## 1.1 说明 基于 httprunner 框架的用例结构,我自己开发了一个 pytest + yaml 的框架,那么是不是重复造轮子呢? 不可否认 httprunner 框架设计非常优秀,但是也有缺点,httprunner3.x 的版本虽然也是基于 pytest 框架设计,结合 yaml 执行用例,但是会生成一个py文件去执行。 在辅助函数的引用也很局限,只能获取函数的返回值,不能在 yaml 中对返回值重新二次取值。 那么我的这个框架,就是为了解决这些痛点。。。。 完整视频教程地址[https://study.163.com/course/courseMain.htm?courseId=1213419817&share=2&shareId=480000002230338](https://study.163.com/course/courseMain.htm?courseId=1213419817&share=2&shareId=480000002230338) (视频收费:包含 pytest + yaml 框架开发教学和使用教学, 买课程的学员可以用新版本更多强大功能,咨询wx:283340479) 本插件可以实现以下优势: - 1、基于 pytest 框架安装插件即可使用,环境非常简单 - 2、只需要写 yaml 文件用例即可运行,使用 pytest 运行的命令 - 3、yaml 文件中支持定义变量 与 引用变量 - 4、extract 功能实现多个接口步骤的参数关联 - 5、全局仅 base_url 功能,yaml 中写相对路径即可 - 6、全局仅登录一次,在用例中自动在请求头部添加 Authorization token认证 - 7、用例参数化 parameters 功能实现 - 8、yaml 中调用 fixture 功能实现 - 9、yaml 中支持自定义函数调用 - 10、yaml 中调用 hooks 功能(sign签名与加解密) - 11、用例分层机制:API和用例层 - 12、支持 logging 日志 - 13、支持 allure 报告 - 14、支持 mysql 数据库增删改查 - 15、支持钉钉机器人通知测试结果和 allure 报告地址 - 16、支持生成随机测试数据,如字符串,姓名,手机号,邮箱等 - 17、根据 swagger.json 自动生成 yaml 文件接口用例 - 18、支持全局代理配置 - 19、全局配置 env 多套环境切换 - 20、CLI 执行用例,完全适配持续集成 CI/CD 流程 - 21、export 导出全局变量 (VIP 付费功能) - 22、飞书群/企业微信群机器人通知 (VIP 付费功能) - 23、引用变量支持过滤器的使用 (VIP 付费功能) - 24、mark 对用例标记功能 (VIP 付费功能) - 25、runtime 断言用例运行时长 (VIP 付费功能) - 26、录制自动生成 yaml 用例 (VIP 付费功能) - 27、mock 拦截请求自定义返回 (VIP 付费功能) - 28、parameters参数化支持模块级别和用例级别,支持笛卡尔积 (VIP 付费功能) - 29、新增仅收集用例失败错误信息和 log 日志(VIP 付费功能) - 30、allure报告自定义内容 (VIP 付费功能) - 31、redis 数据库支持 (VIP 付费功能) - 32、websocket 协议支持 (VIP 付费功能) **联系我们:** - 作者-上海悠悠 微信/QQ交流:283340479 - blog地址 https://www.cnblogs.com/yoyoketang/ ## 1.2 环境准备 最低版本要求 Python 3.8 版本或以上版本. 目前兼容 python3.8, python3.9, python3.10版本 (低于 python3.8 版本无法安装此插件,低版本python不做适配) Pytest 7.2.0+ 最新版可以有最佳体验 pip 安装插件即可使用,不需要下载源码 ``` pip install pytest-yaml-yoyo ``` # 2 快速开始 ## 2.0 快速创建项目demo 使用 `--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 ``` ## 2.1 第一个hello world 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 框架的用例结果,主要是为了大家快速上手,减少新的规则学习 - config 是非必须的里面有 name 用例名称,base_url 和 variables 是可选的 - teststeps 是非必须,用例的步骤,用例步骤是一个array 数组类型,可以有多个步骤 从上面的运行可以看出,request 不是必须的,我们可以直接调用python内置函数print 去打印一些内容了。 ## 2.2 一个简单的 http 请求 以`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 的命令行运行用例 ## 2.3 一个简单的 post 请求 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] ``` ## 2.4 validate校验 比如返回的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 格式,那么可以支持 - jmespath 取值语法: `body.keyname.keyname` - jsonpath 语法: `$..keyname` - re 正则语法: `xx(.+?)xxx` 如果返回的不是 json 格式,那么可以用正则取值 ## 2.5 变量的声明与引用 变量的声明,只支持在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] ``` 运行结果 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/a0.png) # 3.用例编写 ## 3.1 参数关联 ### 3.1.1 extract 提取接口返回参数关联 在自动化用例中,我们经常会看到有人提问,上一个接口的返回的结果,如何取出来给到下个接口的入参。 我们用 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] ``` ### 3.1.2 引用提取结果 上一个接口提取到了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 ``` ### 3.1.3 extract 提取结果二次取值 在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格式,那么可以支持 - jmespath 语法: body.json.username - jsonpath 语法: $..username - re 正则语法:'code: (.+?),' 如果返回的不是json格式,那么可以用正则取值 当一个用例用到多组测试数据的时候,我们必然会用到参数化,接下来看下如何在yaml文件中实现参数化 ## 3.2 全局 Token 管理 我们在使用自动化测试框架的时候,经常会遇到一个需求,希望在全局用例中,仅登录一次,后续所有的用例自动带上请求头部token 或者cookies。 ### 3.2.1 登录fixture 功能 我在 pytest + yaml 框架框架中封装了一个内置 fixture 叫`requests_session`, 它的作用范围是`scope="session"`,也就是全部session用例会话中仅实例化一次。 现在我只需在conftest 中写一个登录的fixture功能,获取token后添加到`requests_session`头部 ```python 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] ``` ### 3.2.2 运行用例 在命令行中输入`pytest`运行 抓包看发过去的请求 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/a1.png) ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/a2.png) 于是可以看到,在2个用例中都自动带上了请求头部参数。 (登录cookies的使用原理也是一样的,登录后cookies一般会自动保持) ### 3.2.3 其它需求 那有些接口不需要登录怎么办呢?比如登录和注册的接口,是不需要带上登录token的,那不能一刀切。 我除了默认用到一个`requests_session` 全局的内置fixture,还预留了2个 - requests_module: 每个yaml文件中用一次 - requests_function: 每个用例中用一次 在yaml文件中切换到指定fixture功能,`requests_module` 和 `requests_function` 后续会实现,先实现大需求,解决大部分人遇到的痛点问题! ### 3.2.4 requests_module 和 requests_function 那有些接口不需要登录怎么办呢?比如登录和注册的接口,是不需要带上登录token的。 除了默认用到一个requests_session 全局的内置fixture,还预留了2个 - requests_module: 每个yaml文件中用一个请求会话(会保持cookies) - requests_function: 每个用例中用一次,每个用例独立运行,不保持cookies 接下来看下如何在用例中使用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。 ### 3.2.5 自定义 fixtures 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 ..... ``` ### 3.2.6 多个fixtures的使用 当 yaml 中的用例需要用到多个fixtures时, 支持2种格式 格式一: 逗号隔开 ``` config: fixtures: fixture_name1, fixture_name2 ``` 格式二: 用 list ``` config: fixtures: [fixture_name1, fixture_name2] ``` requests_module 和 requests_function 内置 fixture 功能在 v1.1.1 版本实现, 版本太低的请及时更新版本。 ## 3.3 parameters 参数化 ### 3.3.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"} ``` ### 3.3.2 使用 fixtures 功能实现参数化 基本实现原理参考 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文件中 - 参数化需要的变量写到 config 的 fixtures 位置 - 参数化需要的数据写到 parameters 示例 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 ================ ``` ### 3.3.3 parameters 实现参数化 第二种实现方式,可以在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种实现参数化方式效果是一样的 ## 3.4 yaml 中调用内置方法 pytest-yaml-yoyo 插件使用了强大的jinja2 模板引擎,所以我们在yaml文件中可以写很多python内置的语法了。 ### 3.4.1 调用 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"} ``` ### 3.4.2 字典对象取值 如果定义一个字典类型的变量,我们在取值的时候也可以根据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}'] ``` ## 3.5 自定义函数功能 一些复杂的逻辑处理,需自己写代码去实现,于是可以自定义函数。 ### 3.5.1 自定义函数 自定义函数的实现,需在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}'] ``` ### 3.5.2 函数传参数 在引用自定义函数的时候,也可以传变量 ``` # 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] ``` ### 3.5.3 函数返回的结果也能二次取值 如果一个函数返回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.5.4 内置函数的使用 目前暂时提供了3个内置函数,和1个内置对象 - current_time(f: str = '%Y-%m-%d %H:%M:%S'), 获取当前时间 默认格式为2022-12-16 22:13:00,可以传f参数自定义格式 - rand_value(target: list) 从返回的 list 结果随机取值, 有小伙伴提到的需求 - rand_str(len_start=None, len_end=None) 生成随机字符串,默认32位 还提供了一个内置的fake 对象,可以生成随机手机号,随机身份证,姓名等数据 使用方法:`${fake.name()}`, `fake.phone_number()`, `fake.email()` 等,具体查看Faker模块提供的方法[https://www.cnblogs.com/yoyoketang/p/14869348.html](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" } ``` ### 3.5.5 fake 对象的使用 内置的 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](https://www.cnblogs.com/yoyoketang/p/14869348.html) ## 3.6 钩子功能 ### 3.6.1 response 钩子功能 在发送请求的时候,我们希望在发送请求参数前,带上签名的值,或者返回的内容需要二次处理,解密后返回。 此功能我们可以用 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 库的钩子功能实现的基本方式 ### 3.6.2 yaml 用例中添加response 钩子 在yaml 文件中添加response 钩子功能,跟上面代码方式差不多, 有2种方式 - 1.写到config 全局配置,每个请求都会带上hooks - 2.写到单个请求的request 下,仅单个请求会带上hooks功能 先看单个请求的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]` 会失败 ### 3.6.3 钩子方法调用语法 - 1.层级是在request 下 - 2.hooks 关键字对应的是一个字典 {"response": []} - 3.response 的值可以是单个函数名称,也可以是多个`func1, func2`,或者是一个list类型[func1, func2] - 4.response 的值必须是一个可以调用的函数,此函数需在conftest 中注册绑定到`my_builtins` 模块 - 5.调用的函数第一个参数是`response`, 可以重写response内容(如需要对返回结果解密),也可以不用重写 ``` request: method: POST url: http://httpbin.org/post hooks: response: ['hook_response'] ``` ### 3.6.4 config 全局钩子使用 在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 ### 3.6.5 请求预处理钩子 如果需要对请求参数预处理,我们还新增了一个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 签名和加密处理等需求。 ## 3.7 用例分层 当我们测试流程类的接口,需反复去调用同一个接口,就会想到复用API,在代码里面可以写成函数去调用。 那么在yaml 文件中,我们可以把单个API写到一个yaml 文件,测试用例去调用导入API。 我这里只分2层:API 层 和 Test case 用例层 - API 层: 描述接口request请求,可以带上validate 基本的校验 - Test case 用例层: 用例层多个步骤按顺序引用API ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/a3.png) ### 3.7.1 API 层示例 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` 命名 ### 3.7.2 TestCase 层 用例层通过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 ``` ### 3.7.3 关于变量 API 层可以引用变量,引用变量的值都是从用例目录的variables 加载的变量,目前只支持config 设置用例全局变量 ``` config: name: login case base_url: http://127.0.0.1:8000 variables: username: "test123" password: "123456" ``` 我们可以理解为API是用例的一个步骤,是用例的一部分,导入过去相当于复制request 请求到用例步骤里面。 ### 3.7.4 关于校验 在API 层可以写一些基础的校验,比如校验状态码,我们一般不在API层写业务逻辑校验。 比如登录的用例,期望结果可以是登录成功,也可以是登录失败,那么业务逻辑的校验,应该在用例层去校验 ``` - name: step login1 api: api/login.yml extract: url: body.url validate: - eq: [status_code, 200] - eq: [ok, true] ``` 如果API 层和用例层都有validate 校验,最后会合并到一起校验。 ## 3.8 写多个用例 一个yaml 文件中可以写多个用例,yaml 文件相当于py模块,每个用例相当于模块里面定义 pytest 的一个函数, 用例名称最好是test开头,如果不是test开头,也会帮你自动拼接成test开头的 ### 3.8.1 实现原理 在 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 ==================================== ``` 可以看出执行效果是完全一样的 ### 3.8.2 重新定义了yaml用例格式 为了框架的可扩展性,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] ``` ### 3.8.3 支持中文命名 用例的函数名称也可以使用中文命名了,这样更直观 ``` 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` (为了兼容大家的使用习惯) ## 3.9 文件上传 v1.1.3版本上实现文件上传 本插件集成了 requests_toolbelt 插件处理`Content-Type: multipart/form-data` 类型文件上传接口。 ### 3.9.1 文件上传multipart/form-data 用fiddler抓包,查看抓到的接口,以下这种接口就是multipart/form-data - Content-Type: multipart/form-data - body参数是这种格式: -----------------------------22165374713946 Content-Disposition: form-data; title="localUrl" yoyoketang.png -----------------------------22165374713946 Content-Disposition: form-data; name="imgFile"; filename="yoyoketang.png" Content-Type: image/png ![](http://images2017.cnblogs.com/blog/1070438/201712/1070438-20171205232930613-2074674964.png) ### 3.9.2 在yaml 文件中示例 在postman 中,可以直接选择一个文件上传,非常方便 ![](https://img2023.cnblogs.com/blog/1070438/202212/1070438-20221217162337002-1574943477.png) 我们在yaml中也一样,支持文件类的参数,需单独拿出来放到 files 字段里面。 test_upfile.yml 示例 ```yaml 文件上传: 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下 ![](https://img2023.cnblogs.com/blog/1070438/202212/1070438-20221217162549076-1639541804.png) files 里面需要传的具体字段,需根据接口文档定义的参数名称。 当然你把其它字符串字段一起放到files 里面也没问题 ```yaml 文件上传: name: upload file request: url: http://127.0.0.1:8000/api/v1/upfile/ method: POST files: title: 文件上传 file: data/abc.jpg ``` (本插件也是根据你是否在 request 中传了 files 字段来判断是不是需要上传文件) ## 3.10 sleep和skip、skipif 功能 环境要求 Python 大于等于3.8版本,(低于python3.8版本不支持) Pytest 7.2.0 最新版 v1.1.4 发布新增3个关键字 - 1.sleep 添加用例之间的sleep 等待时间 - 2.skip 跳过用例功能 - 3.skipif 条件为真时跳过用例 - 4.查找用例规则优化(之前仅支持查找test开头.yml后缀的用例,现在优化成可以支持.yaml 和 .yml 后缀用例, yaml用例名称可以test开头也可以test结尾,跟pytest查找用例规则保持一致) pip 安装插件, 最新版本v1.1.4 ``` pip install pytest-yaml-yoyo ``` ### 3.10.1 sleep 功能示例 sleep 功能实现time.sleep() 等待时间,sleep参数可以是int类型和float 类型 ```yaml get请求: name: GET请求示例 sleep: 5 request: method: GET url: http://httpbin.org/get validate: - eq: [status_code, 200] ``` sleep 也可以是一个变量,引用config设置的变量值 ```yaml 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后,在请求后执行 ### 3.10.2 skip 跳过用例 pytest 实现跳过用例有2种方式 ```python @pytest.mark.skip(reason="no way of currently testing this") def test_the_unknown(): ... ``` 或者在用例里面跳过 ```python import pytest if not pytest.config.getoption("--custom-flag"): pytest.skip("--custom-flag is missing, skipping tests", allow_module_level=True) ``` 本插件采用的第二种实现方式,在用例里面添加pytest.skip() skip 关键字后仅支持一个参数,那就是跳过的原因。使用示例 ```yaml get请求: name: GET请求示例 skip: 功能缺失,暂时跳过此用例 request: method: GET url: http://httpbin.org/get validate: - eq: [status_code, 200] ``` 运行用例可以看到 ![](https://img2023.cnblogs.com/blog/1070438/202302/1070438-20230213143220279-871638858.png) 如果用例是多个步骤组成的,也可以在步骤中跳过 ```yaml 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 跳过的是用例,而不是步骤! ### 3.10.3 skipif 满足条件跳过 skipif 后面参数是一个表达式,当表达式运行结果为真,那么就跳过用例 ```yaml 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满足条件就跳过后面用例 ```yaml 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()函数运行后得到结果,判断是否为真,为真的时候通过当前用例。 # 4.项目配置/报告/日志 ## 4.1 日志功能 pytest 的日志分2个部分: - console 控制台输出的日志 - log_file 保存到本地文件的日志 本插件默认情况下会记录运行日志保存在项目根目录logs下,以当前时间保存txt文本日志内容。 日志默认保存info级别。 console 控制台默认不输出日志 ### 4.1.1 开启 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------ ``` ### 4.1.2 自定义 console 控制台日志 日志的格式和时间格式也可以自定义设置 ``` [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 ``` ### 4.1.3 自定义保存日志文件 本插件默认情况下会记录运行日志保存在项目根目录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 ``` ### 4.1.4 命令行参数配置 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 ``` ## 4.2 allure 报告 本插件是基于 pytest 框架开发的,所以 pytest 的插件都能使用 allure 报告功能在 v1.0.8 版本上实现 ### 4.2.1 allure 环境准备 allure 是一个命令行工具,需要去github上下载最新版[https://github.com/allure-framework/allure2/releases](https://github.com/allure-framework/allure2/releases) allure 命令行工具是需要依赖jdk 环境,环境内容自己去搭建了 ### 4.2.2 生成 allure 报告 在用例所在的目录执行命令, `--alluredir` 是指定报告生成的目录 ``` pytest --alluredir ./report ``` 打开allure 报告执行命令 ``` >allure serve ./report ``` ### 4.2.3 查看报告 首页显示 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/a4.png) 图表查看 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/a5.png) 用例详情根据yaml文件名称和用例名称展示内容 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/a6.png) ## 4.3 全局 base_url 一个完整的url 地址由环境地址和接口地址拼接而成,环境地址是可变的,可以部署到测试环境,uat联调环境等不同的环境。 不管部署到哪个环境,接口的地址是不可变的,通常需要一个全局base_url 地址做到环境可切换。 pip 安装插件 ``` pip install pytest-yaml-yoyo ``` base_url 全局配置功能在 v1.0.9 版本上实现 ### 4.3.1 环境地址 在接口测试中,通常会把环境 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] ``` ### 4.3.2 全局 base_url 配置 从项目的角度讲,测试项目接口的 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 ``` ### 4.3.3 复杂情况 当设置了全局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] ``` ### 4.3.4 使用优先级 环境地址优先级使用如下: 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 ` ## 4.4 全局项目配置 当我们在测试环境写好自动化的代码,领导说你把代码部署到联调环境再测一测,这时候去改用例里面的配置是很痛苦的。 所以我们在设计自动化用例的时候,就先要想到多环境的配置与切换。 ### 4.4.1 多环境配置 如果需用到多套环境 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`. ### 4.4.2 测试环境的 BASE_URL 在上一篇中讲到 [pytest + yaml 框架 -11.全局 base_url 配置](https://www.cnblogs.com/yoyoketang/p/16970491.html) ``` 环境地址优先级使用如下: 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 ### 4.4.3 多个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] ``` ### 4.4.4 其它配置参数 如果配置中需要加其它配置参数 ``` class TestConfig(Config): """测试环境""" BASE_URL = 'http://192.168.1.1:8000' USER = 'test' TEL = '100860000' ``` 那么在用例中引用配置参数可以用 `${env.配置参数}` 取到配置中的值。 ## 4.5 mysql 数据库配置 如果用例中需要执行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 相关的五个配置,那么有个内置的函数可以使用 - query_sql(sql) 查询sql, 查询无结果返回None, 查询只有一个结果返回dict, 查询多个结果返回list of dict - execute_sql(sql) 执行sql, 操作新增,修改,删除的sql ### 4.5.1 断言执行sql 使用示例 ``` 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" ### 4.5.2 用例的参数也可以查询sql 如果用例的参数,需要从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} ``` ### 4.5.3 用例前置和后置执行sql 如果需要在用例的前置和后置中执行sql, 可以用到hook 机制,在请求前和请求后执行函数 参考前面这篇[pytest + yaml 框架 -6.hooks 钩子功能实现](https://www.cnblogs.com/yoyoketang/p/16938512.html) ## 4.6 全局代理 在实际的工作中,有些系统的接口我们无法直接访问,需使用代理去访问,那么就需要在整个项目的用例中配置一个全局代理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 前缀不需要 ### 4.6.1 使用示例 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 ``` # 5.反馈通知 ## 5.1 钉钉机器人通知 当用例执行完成后,希望能给报告反馈,常见的报告反馈有:邮箱/钉钉群/飞书/企业微信 等。 pip 安装插件 ``` pip install pytest-yaml-yoyo ``` 钉钉机器人通知测试结果功能在v1.1.1版本实现 ### 5.1.1 钉钉机器人设置 钉钉机器人的设置请参考官方API文档[https://open.dingtalk.com/document/group/custom-robot-access](https://open.dingtalk.com/document/group/custom-robot-access) 我们主要得到Webhook地址上面的access_token 值 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/access_token.jpg) 自定义关键字,默认:测试报告,也可以自定义 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/report1.jpg) 加签 的值,可以不勾选,也可以勾选。 总的来说,需要记住3个地方: - access_token 复制Webhook地址上面的access_token 值 - 自定义关键字 默认:测试报告,也可以自定义其他名称,如果这里改了,后面的配置的title值也要改成一样 - 加签 的值,可以不勾选,也可以勾选。如果勾选了,后面需配置secret 值 ### 5.1.2 config 中配置 DING_TALK 项 在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: 钉钉群自定义机器人access_token - secret: 机器人安全设置页面勾选"加签"时需要传入的密钥 - param pc_slide: 消息链接打开方式,默认False为浏览器打开,设置为True时为PC端侧边栏打开 - param fail_notice: 消息发送失败提醒,默认为False不提醒,开发者可以根据返回的消息发送结果自行判断和处理 - param title: 首屏会话透出的展示内容 - param text: markdown格式的消息内容 - param is_at_all: @所有人:True,否则为:False(可选), 默认False - param at_mobiles: 被@人的手机号, 手机号可以是一个或者多个,写到list - param at_dingtalk_ids: 被@用户的UserId(企业内部机器人可用,可选),可以是一个或者多个,写到list - param is_auto_at: 是否自动在text内容末尾添加@手机号,默认自动添加,也可设置为False,然后自行在text内容中自定义@手机号的位置,才有@效果,支持同时@多个手机号(可选) 运行用例后会自动在钉钉群发送通知 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/ding3.jpg) ### 5.1.3 加签值配置 如果这里没有勾选 加签 值 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/ding2.jpg) 那么只需要配置一个 access_token 即可 ``` DING_TALK = { "access_token": "d2433d2b16cc85943*********************************", } ``` 如果这里勾选 加签 值, 那么需同时配置 access_token 和 secret 值 ``` DING_TALK = { "access_token": "d2433d2b16cc85943*********************************", "secret": "**************************" } ``` ### 5.1.3 设置@指定的人 奈特指定的人有3个配置可以选择 - is_at_all @所有人:True,否则为:False(可选) - at_mobiles: 被@人的手机号,可以是一个或者多个,写到list - at_dingtalk_ids: 被@用户的UserId(企业内部机器人可用,可选),可以是一个或者多个,写到list 使用示例 ``` DING_TALK = { "access_token": "d2433d2b16cc85****************", "at_mobiles": ["15000xxxxxxx", "15001xxxxxxx"] } ``` 于是就可以看到上图的效果,在内容后面带上`@张三`的样式 ### 5.1.4 设置 title 和 内容 title 的名称必须与自定义关键字名称保持一致 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/t1.jpg) ``` 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报告地址即可 于是看到以下的效果 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/ding1.jpg) 总的来说,整个配置都是傻瓜式的,配置非常简单。 # 6.扩展功能 ## 6.1 swagger.json 自动生成 yaml 接口用例 当项目中有很多个接口的时候,一个个去转成 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 ``` ### 6.1.1 使用示例 目前支持2中方式生成 yaml 用例。 1.如果有本地的 swagger.json 文件,可以放到项目根目录,自己写 a.py 文件 目录结构如下 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/swagger1.jpg) a.py 文件调用插件中的接口即可 ``` from pytest_yaml_yoyo.swagger_parser import SwaggerToYaml # 作者 上海-悠悠 微信:283340479 s = SwaggerToYaml('./swagger.json') s.parse_json() ``` 2. 如果 有在线的swagger.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() ``` ### 6.1.2 yaml 用例自动生成 执行完成后会在当前项目目录按接口模块生成对应的yaml格式用例 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/swagger2.jpg) yaml 文件格式示例 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/swagger3.jpg) ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/swagger4.jpg) 生成的用例没有base_url ,只有接口的相对地址,那么可以在当前目录下创建pytest.ini 文件 ``` [pytest] base_url = http://httpbin.org ``` 相关 功能参考全局 base_url 的设置文档[https://www.cnblogs.com/yoyoketang/p/16970491.html](https://www.cnblogs.com/yoyoketang/p/16970491.html) 备注说明: 1.并不是所有的swagger.json格式都支持,目前我是按flask项目生成的swagger.json 格式来解析的,其它的未知,思路供学习和参考。 2.目前只实现基础功能,需写上面代码调用此功能,暂不支持命令行操作 3.目前仅仅是抓取接口和请求参数,自动生成用例结构 4.参数部分拿文档的默认值,还需自己去调试,写对应的测试数据 5.后续想法是根据参数的范围,按等价类,边界值去生成对应的用例 6.断言部分功能未实现,目前仅断言状态码为200 # 7.VIP 付费功能 付费用户可获取最新版本功能,和版本升级服务 ## 7.1 录制功能自动转 yaml 用例 ### 7.1.1 环境准备 录制功能环境没给大家预装,考虑大家 python 版本不太一样,可能有的人装不上。官方文档地址[https://docs.mitmproxy.org/stable/](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] ``` ### 7.1.2 常用的参数配置 `RecoderHTTP` 实例化时,可以设置以下几个参数 - filter_host: 抓取的环境地址,可以是多个 - ignore_cookies: 是否忽略掉cookies,默认False - save_base_url: 是否在 pytest.ini 保存全局base_url环境地址, 默认False - save_case_dir: 设置用例保存目录,默认cases 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 命令就能执行用例了 ## 7.2 mock 功能 前面已经通过代理实现了抓包自动生成 yaml 用例的功能,通过代理也可以实现 mock 功能。 mock 有2种场景: 1.直接拦截发出去的请求,还未到达服务端,模拟自定义返回结果 2.发出去的请求,服务端有反回,拦截返回的结果,篡改返回内容,模拟自己需要的数据 ### 7.2.1 拦截发出去的请求 先看第一种场景:直接拦截发出去的请求,还未到达服务端,模拟自定义返回结果 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/mock1.png) 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/` 地址,就会看到模拟的返回结果 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/mock11.png) ### 7.2.2 拦截返回的结果,篡改返回内容 第二种场景:发出去的请求,服务端有反回,拦截返回的结果,篡改返回内容,模拟自己需要的数据 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/mock2.png) 使用示例:访问`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() ] ``` ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/mock22.png) ## 7.3 mark 标记功能 pytest可以支持对用例自定义标记, 可以把用例按自己的需要归类标记,比如按用例优秀级,标记一些smoke冒烟测试用例。 ### 7.3.1 pytest 标记基本使用 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 ================ ``` ### 7.3.2 yaml 用例中加 mark 标记 yaml 用例中支持2个地方加标记 - config 中使用mark, 作用是当前yaml 文件中的全部用例打上标记 - case 用例中加mark,只针对单个用例打上标记 需注意的是一个用例可以打多个标记,mark 关键字可以是一个字符串,如果是多个标记,可以用`mark: name1, name2` 或 `mark: [name1, name2]` 两种格式 test_m.yml ```yaml 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 ```yaml 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 ``` ## 7.4 mark 标记对用例运行时长断言 pytest 执行用例的时候,我们希望对用例的运行时间断言,当用例执行时长大于预期标记此用例失败。 `@pytest.mark.runtime(1)` 运行时长单位是秒 此插件已打包上传到pypi [https://pypi.org/project/pytest-runtime-yoyo/](https://pypi.org/project/pytest-runtime-yoyo/) 环境准备 ``` pip install pytest-yaml-yoyo ``` 此功能在v1.3.1 版本上实现 ### 7.4.1 代码中实现 基本示例 test_demo.py ```python 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 =============== ``` ### 7.4.2 在 yaml 用例中实现 在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 ### 7.4.3 全局用例配置 对全部用例设置 runtime 标记,可以在 `pytest.ini` 中设置全局配置 ``` [pytest] runtime = 3 ``` 也可以在执行 pytest 命令的时候带上命令行参数`--runtime` ``` pytest --runtime=3 ``` 优先级是: 命令行参数 > pytest.ini 配置 全局配置只针对yaml 中config,测试用例没标记 runtime 的用例生效。 如果yaml 中config,测试用例有标记 runtime,那么优先级是大于全局配置的。 ## 7.5 export 提升全局变量,支持跨yaml文件用例传参 export 导出功能, config 和 case 中都能添加。 简单来说,就是test_a.yml 执行完成后,提取了变量x, 在后面的test_b.yml 和 test_c.yml 中可以直接引用变量x了。 ### 7.5.1 extract 提取变量 在单个测试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, 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, 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, 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, 2023-05-14 10:15:22 [INFO]: validate 校验内容-> [] 2023-05-14 10:15:22 [INFO]: export 导出全局变量:{} PASSED [100%] =============== 3 passed in 0.38s == ``` ### 7.5.2 export 提升全局变量 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, 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 ``` ### 7.5.3 变量优先级 在整个用例中有 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, 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, 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` ## 7.6 支持飞书机器人通知 ### 7.6.1 自定义飞书机器人 飞书机器人配置详细文档参考:[https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN](https://open.feishu.cn/document/ukTMukTMukTM/ucTM5YjL3ETO24yNxkjN) 第一步:邀请自定义机器人入群 进入你的目标群组,打开会话设置,找到群机器人,并点击添加机器人,选择自定义机器人加入群聊。 第二步: 设置机器人名称和描述 为你的机器人输入一个合适的名字和描述,也可以为机器人设置一个合适的头像,然后点击下一步。 第三步:复制 webhook 地址 webhook地址复制出来:`https://open.feishu.cn/open-apis/bot/v2/hook/xxxxxxxxxxxxxxxxx` `hook/` 后面的一串 `xxxxxxxxxxxxxxxxx` 就是我们要的 token ### 7.6.2 config 配置 FEI_SHU 在项目根目录(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 ``` ### 7.6.3 运行用例生成报告 通过pytest 命令行运行用例 ``` pytest ``` 运行完成后,会根据你配置的token内容,触发飞书通知 ``` # 作者-上海悠悠 wx:283340479 # blog地址 https://www.cnblogs.com/yoyoketang/ # 飞书机器人通知 FEI_SHU = { "token": "*****************", # 必须 # "text": "- 查看报告:[allure报告地址](https://www.cnblogs.com/yoyoketang/)" # 非必须 } ``` 用例成功,背景显示绿色 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/fei1.png) 用例失败,背景显示红色 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/fei2.png) ## 7.7 企业微信机器人通知 ### 7.7.1 获取企业微信机器人token 企业机器人相关接口可以看官方文档[https://developer.work.weixin.qq.com/document/path/91770](https://developer.work.weixin.qq.com/document/path/91770) 创建群聊机器人 获取到webhook访问地址`https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=*********************` key= 后面的这串就是我们要的token ### 7.7.2 配置企业微信机器人通知 在运行环境配置中加一个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 ``` 执行用例就会自动触发企业微信机器人 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/we1.png) ## 7.8 模板过滤器语法与自定义过滤器使用 针对有同学提到上个接口返回一个id值,下个接口引用变量的时候需要根据这个值做一些运算,比如在引用的结果加1. 本篇介绍下模板过滤器的相关使用. ### 7.8.1 jinja2 模板过滤器语法 什么是 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}` 的处理过程如下: - 输入变量 var 传递给第一个过滤器 fiterA; - 将过滤器 filterA 的输出作为输入,传递给第二个过滤器 filterB; - 将过滤器 filterB 的输出作为模板的输出。 例如:`${"abc" | upper | reverse }` 的输出为 "CBA"。 ### 7.8.2 常用的过滤器 |过滤器名称|语法使用 |实现功能 | |---| -------- | -------- | |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模板引擎自带的过滤器方法。 ### 7.8.3 使用示例 在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, 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, 2023-05-23 10:16:48 [INFO]: validate 校验内容-> [] 2023-05-23 10:16:48 [INFO]: export 导出全局变量:{} PASSED [100%] ``` ### 7.8.4 自定义过滤器 自定义过滤器语法跟自定义函数有点类似,也是在项目根路径下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, 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, 2023-05-23 10:38:48 [INFO]: validate 校验内容-> [] 2023-05-23 10:38:48 [INFO]: export 导出全局变量:{} PASSED [100%] ``` ## 7.9 支持自定义函数提取返回结果 在 yaml 用例中提取返回结果,可以支持以下三种表达式 - jmespath 取值语法: body.keyname.keyname - jsonpath 语法: $..keyname - re 正则语法 以上三种表达式可以满足 99% 的测试场景需求了,但是有些特殊的需求通过表达式无法取到,为了满足另外1%的需求,可以自定义函数取值。 此功能在v1.3.6版本实现 ### 7.9.1 场景描述 有个小伙伴给我提了个需求:如果返回的结果中有某个值就断言,没有就不断言 示例:如下返回结果,当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" } ] } ``` ### 7.9.2 代码示例 先自定义函数取值,传一个 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 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 导出全局变量:{} ``` ## 7.10 parameters参数化支持笛卡尔积 v1.3.8 版本对 parameters 参数化格式重新做了定义,支持笛卡尔积了。当然以前旧版本的格式还是继续兼容。 ### 7.10.1 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 . ``` ### 7.10.2 笛卡尔积 对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 . ``` ### 7.10.3 模块级别和用例级别参数化 支持模块级别和用例级别参数化 - config 中 parameters 参数化,作用域是整个模块级别 - 用例中 parameters 参数化,作用域只针对单个用例 用例级别参数化 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 . ``` ## 7.11 仅收集用例失败错误信息和log日志 有小伙伴提到能不能只收集用例失败的情况下日志和错误信息,每个用例都收集日志内容太多了,没法直观的看错误的用例信息。 v1.3.9 版本实现此功能,在原有的功能上新增一个log日志文件,仅收集用例错误的日志。 ### 7.11.1 log 日志收集 用例执行完会默认生成2个日志文件 - 全量日志,每个用例的日志都会收集 - 仅失败的用例日志 ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/log1.png) 使用示例 ``` 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: == 201->\nassert 200 == 201") tblen=4> exception详细日志:request = > requests_session = > ??? 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-> == 201-> E assert 200 == 201 venv\lib\site-packages\pytest_yaml_yoyo\validate.py:10: AssertionError Captured stdout call2222 Captured log callINFO  pytest_yaml_yoyo.log:runner.py:128 执行文件-> test_a2.yml INFO  pytest_yaml_yoyo.log:runner.py:129 base_url-> http://124.70.221.221:8201 INFO  pytest_yaml_yoyo.log:runner.py:130 config variables-> {} INFO  pytest_yaml_yoyo.log:runner.py:132 运行用例-> test_2 INFO  pytest_yaml_yoyo.log:runner.py:221 validate 校验内容-> [{'eq': [200, 201]}] INFO  pytest_yaml_yoyo.log:runner.py:557 validate 校验结果-> eq: [200, 201] ``` 根据用例节点`a1/test_a2.yml::test_2` 详细记录用例报错的内容和用例运行的日志. ## 7.12 allure报告自定义内容 v1.4.0 版本支持allure报告自定义内容 ### 7.12.1 用例添加 allure 描述 用例中可以通过dynamic 添加以下内容 - allure.dynamic.feature - allure.dynamic.link - allure.dynamic.issue - allure.dynamic.testcase - allure.dynamic.story - allure.dynamic.title - allure.dynamic.description 在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 ``` ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/allure1.png) ### 7.12.2 用例有多个步骤,添加step描述 在test_b.yml 用例中有多个步骤: - allure 描述可以放到第一个步骤 - 其它步骤中的name对应的值,就是每个步骤的名称 - 如果allure 中没有定义feature值,那么默认读取yaml 文件的名称 test_b.yml 示例 ``` config: name: 用例描述 test_b2: - allure: title: 用例有多个步骤情况 - name: 步骤1 print: hello - name: 步骤2 print: world ``` ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/allure2.png) ### 7.12.3 config 中定义公共allure 同一个 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 ``` ![](https://gitee.com/yoyoketang/pytest-yaml-yoyo/raw/master/tests/images/allure3.png) ## 7.13 redis 配置 v1.4.1 新增 redis 数据库配置 ### 7.13.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 ``` ### 7.13.2 yaml 用例中操作redis 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] ``` ## 7.14 支持 websocket 协议 v1.4.2 版本支持 websocket 协议 ### 7.14.1 python 操作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"} ``` ### 7.14.2 yaml 中 webscoket 用例实现 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"}'] ``` ### 7.14.3 多个连接地址测试 一个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 ============================ ``` # 8.版本变更记录 v1.0.0 发布的第一个版本(已删除) v1.0.1 发布时间 2022.11.23 可以安装的第一个版本 - 1.实现基础的 pytest 命令 执行yaml 文件用例功能 v1.0.2 发布时间 2022.11.24 - 1.新增 extract 关键字,在接口中提取返回结果 - 2.参数关联,上一个接口的返回值可以作为下个接口的入参 详细功能参阅 extract 关键字文档 v1.0.3 发布时间 2022.11.28 - 1.config 新增 fixtures 关键字,在yaml 用例中传 fixture 功能和参数化功能 - 2.config 新增 parameters 用例参数化实现 详细功能参阅 parameters 参数化 关键字文档 v1.0.4 发布时间 2022.11.30 hooks 钩子功能实现 - 1.request 钩子对请求参数预处理 - 2.response 钩子对返回结果处理 详细功能参阅 hooks 钩子 关键字文档 v1.0.5 发布时间 2022.12.05 用例分层机制 - 1.API 层对接口的描述,可以复用 - 2.Test case 用例层引用API层 v1.0.6 发布时间 2022.12.06 一个yaml 中写多个用例,用例步骤可以不是list 类型 - 1.config 和 teststeps 不是必须了 - 2.可以自定义用例名称,用例可以是一个步骤也可以是多个步骤 v1.0.7 发布时间 2022.12.08 新增日志 - 1.日志默认按日期时间保存到 logs 目录 - 2.console 日志开启在 pytest.ini 配置,或命令行参数 v1.0.8 发布时间 2022.12.09 结合 allure 生成报告 v1.0.9 发布时间 2022.12.09 全局 base_url 配置 - 1.pytest.ini 新增 `base_url` 配置 - 2.命令行新增 `--base-url` 参数 v1.1.0 发布时间 2022.12.13 多环境配置切换 - 1.config.py 实现多环境配置 - 2.pytest.ini 新增 `env` 配置, 命令行新增 `--env` 参数 - 3.支持 mysql 数据库操作 - 4.支持 yaml 中引用 env 环境对象 v1.1.1 发布时间 2022.12.14 钉钉机器人通知测试结果 - 1.config.py 配置钉钉机器人access_token - 2.测试结果钉钉群通知 - 3.支持 requests_function 和 requests_module 内置 fixture 切换 v1.1.2 发布时间 2022.12.16 内置方法提供 - 1.提供3个常用的内置函数:current_time, rand_value, rand_str - 2.一个内置fake对象 - 3.修复yaml文件为空或格式不正确时,执行报错的问题 v1.1.3 发布时间 2022.12.17 - 文件上传multipart/form-data 格式支持 v1.1.4 发布时间 2023.2.13 新增3个关键字 - 1.sleep 添加用例之间的sleep 等待时间 - 2.skip 跳过用例功能 - 3.skipif 条件为真时跳过用例 v1.1.5 发布时间 2023.2.16 支持 2 中方式生成 yaml 用例 - 1.本地 swagger.json 文件 - 2.在线 swagger.json 地址 v1.1.6 发布时间 2023.2.17 支持2种方式实现 - 1.在命令行执行的时候带上 `--proxies-ip=代理ip:端口` - 2.在 pytest.ini 添加全局配置`--proxies-ip=代理ip:端口` v1.1.8 发布时间 2023.3.17 int 转 str 类型 v1.1.9 发布时间 2023-03-21 做了以下优化 - 1.validate 校验加了text 关键字获取全部body文本内容 - 2.用例分层 api和 testcase 层 validate 校验优化,解决之前遗留的bug - 3.validate 校验方式更灵活,支持int类型校验字符长度和包含字符 - 4.log 日志文件优化,只保留最近的5个日志文件 v1.2.0 发布时间 2023-05-08 优化以下问题 - 1.断言的时候 None 和 'None' 可以判断是相等,在yaml中可以写null 或者 None, 不区分类型了 - 2.添加局部变量variables - 3.优化request 下的hook 功能 - 4.其它细节优化 v1.2.1 发布时间 2023-05-20 优化以下问题 - 1.兼容python3.8, python3.9, python3.10版本 - 2.支持在case 用例中针对单个用例的参数化了 - 3.参数化数据支持读取外部文件,文件格式可以支持:`txt/csv/json/yaml` - 4.函数的参数可以引用变量了,如: `${fun("hello ${key}")}` - 5.内置`to_json()` 函数,字典转 json v1.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 协议 # 9 联系我们 作者-上海悠悠 微信/QQ交流:283340479 blog地址 https://www.cnblogs.com/yoyoketang/ 完整视频教程地址[https://study.163.com/course/courseMain.htm?courseId=1213419817&share=2&shareId=480000002230338](https://study.163.com/course/courseMain.htm?courseId=1213419817&share=2&shareId=480000002230338) (视频收费:包含 pytest + yaml 框架开发教学和使用教学, 买课程的学员可以用新版本更多强大功能,咨询wx:283340479)