apimeter


Nameapimeter JSON
Version 2.10.0 PyPI version JSON
download
home_pagehttps://github.com/httprunner/httprunner
SummaryOne-stop solution for HTTP(S) testing.
upload_time2025-07-21 08:19:26
maintainerNone
docs_urlNone
authordebugtalk
requires_python<4.0,>=3.6
licenseApache-2.0
keywords http api test requests locustio
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # ApiMeter

*ApiMeter* is a simple & elegant, yet powerful HTTP(S) API testing framework, base on HttpRunner v2.5.9. Enjoy! ✨ 🚀 ✨

## 支持新特性
1. 自定义函数的参数支持引用全局变量
```yaml
- eq: 
    - ${validate_token_v2(content)}
    - true
```    

2. 自定义函数的参数支持引用全局变量的链式取值
```yaml
- eq: 
    - ${validate_token(content.token)}
    - true
```    

3. 自定义函数的参数支持引用自定义变量链式取值
```yaml
- eq: 
    - ${validate_token($resp.token)}
    - true
```    

4. 自定义函数支持列表参数解析
```yaml
sign: ${get_sign_v2([$device_sn, $os_platform, $app_version])}
```

5. 自定义函数支持字典对象参数解析
```
sign: "${get_sign_v3({device_sn: $device_sn, os_platform: $os_platform, app_version: $app_version})}"
``` 

6. 自定义函数支持复杂嵌套对象参数解析
```yaml
- eq:
    - "${check_nested_list_fields_not_empty(content, {list_path: productList, nested_list_field: sku, check_fields: [id, amount, origin_amount, currency, account_number, duration]})}"
    - True
```    

7. 自定义函数支持链式参数|通配符参数|正则表达式参数解析
```yaml
- eq:
    - ${check(content, data.product.purchasePlan.*.sku.*.id, data.product.purchasePlan.*.sku.*.amount, data.product.purchasePlan.*.sku.*.origin_amount, data.product.purchasePlan.*.sku.*.currency, data.product.purchasePlan.*.sku.*.account_number, data.product.purchasePlan.*.sku.*.duration)}
    - True
- eq:
    - ${check(content, '_url ~= ^https?://[^\s/$.?#].[^\s]*$', 'default_currency =* [USD, CNY]', 'default_sku @= dict', 'sku @= list', 'product @= dict')} # 一次性校验所有字段
    - True    
```

8. 内置全局变量

全局变量支持在用例中直接使用和或作为参数入参,无需添加前缀:$。同时,支持全局变量转义功能,使用反斜杠'\'将全局变量名作为字面量字符串使用。

    - content / body / text / json
    - status_code
    - cookies
    - elapsed
    - headers
    - encoding
    - ok
    - reason
    - url
```yaml
# 使用示例
status_code
content
content.person.name.first_name
body
body.token
headers
"headers.content-type"
cookies
elapsed.total_seconds

# 特殊情况:当数据字段与全局变量同名时,支持使用反斜杠'\'转义全局变量,将其作为字面量字符串处理
- eq:
    - ${check_data_not_null(content.data.linesCollectList.data,2,lines,\content)}
    - True
# 这里 \content 会被解析为字符串 "content",而不是全局变量 content 的值
# 支持转义所有全局变量:\content, \body, \text, \json, \status_code, \headers, \cookies, \encoding, \ok, \reason, \url
```



## Document

1. ApiMeter 用户使用文档:[https://utils.git.umlife.net/apimeter](https://utils.git.umlife.net/apimeter/)
2. ApiMeter PYPI发布版本:[https://pypi.org/project/apimeter](https://pypi.org/project/apimeter)

## Usage
```python
pip install apimeter  # 安装,安装后可用内置命令:apimeter、hrun、apilocust
apimeter /path/to/api  # 完整生成报告
apimeter /path/to/api --skip-success  # 报告忽略成功用例数
```


## Development
```python
# 本地开发与运行
poetry install  # 拉取代码后安装依赖
poetry run python -m apimeter /path/to/api  # 完整生成报告
poetry run python -m apimeter /path/to/api --skip-success  # 报告忽略成功用例数据
python -m apimeter -h # 查看使用指南


# 测试运行
python -m unittest discover # 运行所有单元测试
python -m unittest tests/test_context.py # 运行指定测试文件
python -m unittest tests.test_api.TestHttpRunner.test_validate_response_content # 运行单个测试用例

python -m tests.api_server 或 PYTHONPATH=. python tests/api_server.py # 启动测试示例服务器
python -m apimeter tests/demo/demo.yml
python -m apimeter tests/testcases --log-level debug --save-tests # 测试示例,同时设置日志与生成中间处理文件


# 打包编译与发布
git tag v1.0.0 或 git tag -a v1.0.0 -m "发布正式版本 v1.0.0" # 打标签(轻量或附注)
git push v1.0.0 或 git push --tags # 推送标签(单个或所有)
poetry build  # 打包
poetry publish  # 发布,根据提示输入pypi账号密码
pip install -i https://pypi.Python.org/simple/ apimeter  # 指定安装源,因为刚发布其他平台未及时同步


# 文档编译与部署 .gitlab-ci.yml(设置apimeter-部署-Pages/完善功能文档,更新mkdocs.yml配置)
pip install mkdocs-material==3.3.0
mkdocs build
mkdocs serve


# 逐行代码运行时内存分析
poetry shell
pip install memory-profiler
# 1. 导入方式
python -m apimeter ~/Project/ATDD/tmp/demo_api/ --skip-success
# 2. 装饰器方式
python -m memory_profiler apimeter ~/Project/ATDD/tmp/demo_api --skip-success --log-level error
# 3. 命令方式
mprof run apimeter /path/to/api
mprof plot  # 生成内存趋势图,安装依赖pip install matplotlib
# 参考链接:https://www.cnblogs.com/rgcLOVEyaya/p/RGC_LOVE_YAYA_603days_1.html
```

## Validate

### 1、校验器支持两种格式
```yaml
- {"check": check_item, "comparator": comparator_name, "expect": expect_value}  # 一般格式
- comparator_name: [check_item, expect_value]  # 简化格式
```

### 2、支持自定义校验器
对于自定义的校验函数,需要遵循三个规则:
- (1)自定义校验函数需放置到debugtalk.py中;
- (2)参数有两个:第一个为原始数据,第二个为原始数据经过运算后得到的预期结果值;
- (3)在校验函数中通过assert将实际运算结果与预期结果值进行比较;
```yaml
# 用例
- test:
    name: get token
    request:
        url: http://127.0.0.1:5000/api/get-token
        method: GET
    validate:
        - {"check": "status_code", "comparator": "eq", "expect": 200}
        - {"check": "status_code", "comparator": "sum_status_code", "expect": 2}

# 自定义校验器
def sum_status_code(status_code, expect_sum):
    """ sum status code digits
        e.g. 400 => 4, 201 => 3
    """
    sum_value = 0
    for digit in str(status_code):
        sum_value += int(digit)
    assert sum_value == expect_sum
```

### 3、支持在校验器中引用变量
在结果校验器validate中,check和expect均可实现实现变量的引用;而引用的变量,可以来自四种类型:
- (1)当前test中定义的variables,例如expect_status_code
- (2)当前test中提取(extract)的结果变量,例如token
- (3)当前测试用例集testset中,先前test中提取(extract)的结果变量
- (4)当前测试用例集testset中,全局配置config中定义的变量
```yaml
- test:
    name: get token
    request:
      url: http://127.0.0.1:5000/api/get_token
      method: GET
    variables:
      - expect_status_code: 200
      - token_len: 16
    extract:
      - token: content.token
    validate:
      - {"check": "status_code", "comparator": "eq", “expect": "$expect_status_code"}
      - {"check": "content.token", "comparator": "len_eq", "expect": "$token_len"}
      - {"check": "$token", "comparator": "len_eq", "expect": "$token_len"}
```
基于引用变量的特效,可实现更灵活的自定义函数校验器
```yaml
- test:
    name: get token
    request:
        url: http://127.0.0.1:5000/api/get-token
        method: GET
    validate:
        - {"check": "status_code", "comparator": "eq", "expect": 200}
        - {"check": "${sum_status_code(status_code)}", "comparator": "eq", "expect": 2}

# 自定义函数
def sum_status_code(status_code):
    """ sum status code digits
        e.g. 400 => 4, 201 => 3
    """
    sum_value = 0
    for digit in str(status_code):
        sum_value += int(digit)
    return sum_value
```

### 4、支持正则表达式提取结果校验内容
假设接口的响应结果内容为LB123abcRB789,那么要提取出abc部分进行校验:
```yaml
- test:
    name: get token
    request:
        url: http://127.0.0.1:5000/api/get-token
        method: GET
    validate:
        - {"check": "LB123(.*)RB789", "comparator": "eq", "expect": "abc"}
```


### 5、内置校验器
| Comparator       | Description                    | A(check), B(expect)          | Examples                                     |
|------------------|--------------------------------|------------------------------|----------------------------------------------|
| eq               | value is equal                 | A == B                       | 9 eq 9                                       |
| lt               | less than                      | A < B                        | 7 lt 8                                       |
| le               | less than or equals            | A <= B                       | 7 le 8, 8 le 8                               |
| gt               | greater than                   | A > B                        | 8 gt 7                                       |
| ge               | greater than or equals         | A >= B                       | 8 ge 7, 8 ge 8                               |
| ne               | not equals                     | A != B                       | 6 ne 9                                       |
| str_eq           | string equals                  | str(A) == str(B)             | 123 str_eq '123'                             |
| len_eq, count_eq | length or count equals         | len(A) == B                  | 'abc' len_eq 3, [1,2] len_eq 2               |
| len_gt, count_gt | length greater than            | len(A) > B                   | 'abc' len_gt 2, [1,2,3] len_gt 2             |
| len_ge, count_ge | length greater than or equals  | len(A) >= B                  | 'abc' len_ge 3, [1,2,3] len_ge 3             |
| len_lt, count_lt | length less than               | len(A) < B                   | 'abc' len_lt 4, [1,2,3] len_lt 4             |
| len_le, count_le | length less than or equals     | len(A) <= B                  | 'abc' len_le 3, [1,2,3] len_le 3             |
| contains         | contains                       | [1, 2] contains 1            | 'abc' contains 'a', [1,2,3] len_lt 4         |
| contained_by     | contained by                   | A in B                       | 'a' contained_by 'abc', 1 contained_by [1,2] |
| type_match       | A is instance of B             | isinstance(A, B)             | 123 type_match 'int'                         |
| regex_match      | regex matches                  | re.match(B, A)               | 'abcdef' regex 'a|w+d'                       |
| startswith       | starts with                    | A.startswith(B) is True      | 'abc' startswith 'ab'                        |
| endswith         | ends with                      | A.endswith(B) is True        | 'abc' endswith 'bc'                          |



## 注意事项
```yaml
# 用例skip机制,支持用例层和API层
1. 无条件跳过:skip: skip this test unconditionally
2. 自定义函数返回True:skipIf: ${skip_test_in_production_env()}
3. 自定义函数返回False:skipUnless: ${skip_test_in_production_env()}

# 日志输出需要指定绝对路径或相对路径,不能指定单独一个文件名(文件可以未创建)
hrun --log-level debug --log-file ./test.log   api/youcloud/query_product_api.yml

# 自定义函数使用了字典参数,需要使用双引号包围,避免YAML解析器会将其误认为是字典定义。例如:
sign: "${get_sign_v3({device_sn: $device_sn, os_platform: $os_platform, app_version: $app_version})}"

# $转义
$$

# 一键打包发布,更多内容参考 scripts
make release-patch  MESSAGE="支持自动化打包发布,发布版本v2.8.4" # 自动累积小版本
make quick-release VERSION=2.85 MESSAGE="完善使用说明文档,发布版本v2.8.5" # 跳过单元测试
```

## 附录-相关链接
- HttpRunner: https://github.com/httprunner/
- Requests: http://docs.python-requests.org/en/master/
- unittest: https://docs.python.org/3/library/unittest.html
- Locust: http://locust.io/
- har2case: https://github.com/httprunner/har2case
- HAR: http://httparchive.org/
- Swagger: https://swagger.io/
            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/httprunner/httprunner",
    "name": "apimeter",
    "maintainer": null,
    "docs_url": null,
    "requires_python": "<4.0,>=3.6",
    "maintainer_email": null,
    "keywords": "HTTP, api, test, requests, locustio",
    "author": "debugtalk",
    "author_email": "debugtalk@gmail.com",
    "download_url": "https://files.pythonhosted.org/packages/44/bb/0b664984d231c29f203c0ddddd8431c8b87ace18fbed90426259a3d451e3/apimeter-2.10.0.tar.gz",
    "platform": null,
    "description": "# ApiMeter\n\n*ApiMeter* is a simple & elegant, yet powerful HTTP(S) API testing framework, base on HttpRunner v2.5.9. Enjoy! \u2728 \ud83d\ude80 \u2728\n\n## \u652f\u6301\u65b0\u7279\u6027\n1. \u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u53c2\u6570\u652f\u6301\u5f15\u7528\u5168\u5c40\u53d8\u91cf\n```yaml\n- eq: \n    - ${validate_token_v2(content)}\n    - true\n```    \n\n2. \u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u53c2\u6570\u652f\u6301\u5f15\u7528\u5168\u5c40\u53d8\u91cf\u7684\u94fe\u5f0f\u53d6\u503c\n```yaml\n- eq: \n    - ${validate_token(content.token)}\n    - true\n```    \n\n3. \u81ea\u5b9a\u4e49\u51fd\u6570\u7684\u53c2\u6570\u652f\u6301\u5f15\u7528\u81ea\u5b9a\u4e49\u53d8\u91cf\u94fe\u5f0f\u53d6\u503c\n```yaml\n- eq: \n    - ${validate_token($resp.token)}\n    - true\n```    \n\n4. \u81ea\u5b9a\u4e49\u51fd\u6570\u652f\u6301\u5217\u8868\u53c2\u6570\u89e3\u6790\n```yaml\nsign: ${get_sign_v2([$device_sn, $os_platform, $app_version])}\n```\n\n5. \u81ea\u5b9a\u4e49\u51fd\u6570\u652f\u6301\u5b57\u5178\u5bf9\u8c61\u53c2\u6570\u89e3\u6790\n```\nsign: \"${get_sign_v3({device_sn: $device_sn, os_platform: $os_platform, app_version: $app_version})}\"\n``` \n\n6. \u81ea\u5b9a\u4e49\u51fd\u6570\u652f\u6301\u590d\u6742\u5d4c\u5957\u5bf9\u8c61\u53c2\u6570\u89e3\u6790\n```yaml\n- eq:\n    - \"${check_nested_list_fields_not_empty(content, {list_path: productList, nested_list_field: sku, check_fields: [id, amount, origin_amount, currency, account_number, duration]})}\"\n    - True\n```    \n\n7. \u81ea\u5b9a\u4e49\u51fd\u6570\u652f\u6301\u94fe\u5f0f\u53c2\u6570\uff5c\u901a\u914d\u7b26\u53c2\u6570\uff5c\u6b63\u5219\u8868\u8fbe\u5f0f\u53c2\u6570\u89e3\u6790\n```yaml\n- eq:\n    - ${check(content, data.product.purchasePlan.*.sku.*.id, data.product.purchasePlan.*.sku.*.amount, data.product.purchasePlan.*.sku.*.origin_amount, data.product.purchasePlan.*.sku.*.currency, data.product.purchasePlan.*.sku.*.account_number, data.product.purchasePlan.*.sku.*.duration)}\n    - True\n- eq:\n    - ${check(content, '_url ~= ^https?://[^\\s/$.?#].[^\\s]*$', 'default_currency =* [USD, CNY]', 'default_sku @= dict', 'sku @= list', 'product @= dict')} # \u4e00\u6b21\u6027\u6821\u9a8c\u6240\u6709\u5b57\u6bb5\n    - True    \n```\n\n8. \u5185\u7f6e\u5168\u5c40\u53d8\u91cf\n\n\u5168\u5c40\u53d8\u91cf\u652f\u6301\u5728\u7528\u4f8b\u4e2d\u76f4\u63a5\u4f7f\u7528\u548c\u6216\u4f5c\u4e3a\u53c2\u6570\u5165\u53c2\uff0c\u65e0\u9700\u6dfb\u52a0\u524d\u7f00\uff1a$\u3002\u540c\u65f6\uff0c\u652f\u6301\u5168\u5c40\u53d8\u91cf\u8f6c\u4e49\u529f\u80fd\uff0c\u4f7f\u7528\u53cd\u659c\u6760'\\'\u5c06\u5168\u5c40\u53d8\u91cf\u540d\u4f5c\u4e3a\u5b57\u9762\u91cf\u5b57\u7b26\u4e32\u4f7f\u7528\u3002\n\n    - content / body / text / json\n    - status_code\n    - cookies\n    - elapsed\n    - headers\n    - encoding\n    - ok\n    - reason\n    - url\n```yaml\n# \u4f7f\u7528\u793a\u4f8b\nstatus_code\ncontent\ncontent.person.name.first_name\nbody\nbody.token\nheaders\n\"headers.content-type\"\ncookies\nelapsed.total_seconds\n\n# \u7279\u6b8a\u60c5\u51b5\uff1a\u5f53\u6570\u636e\u5b57\u6bb5\u4e0e\u5168\u5c40\u53d8\u91cf\u540c\u540d\u65f6\uff0c\u652f\u6301\u4f7f\u7528\u53cd\u659c\u6760'\\'\u8f6c\u4e49\u5168\u5c40\u53d8\u91cf\uff0c\u5c06\u5176\u4f5c\u4e3a\u5b57\u9762\u91cf\u5b57\u7b26\u4e32\u5904\u7406\n- eq:\n    - ${check_data_not_null(content.data.linesCollectList.data,2,lines,\\content)}\n    - True\n# \u8fd9\u91cc \\content \u4f1a\u88ab\u89e3\u6790\u4e3a\u5b57\u7b26\u4e32 \"content\"\uff0c\u800c\u4e0d\u662f\u5168\u5c40\u53d8\u91cf content \u7684\u503c\n# \u652f\u6301\u8f6c\u4e49\u6240\u6709\u5168\u5c40\u53d8\u91cf\uff1a\\content, \\body, \\text, \\json, \\status_code, \\headers, \\cookies, \\encoding, \\ok, \\reason, \\url\n```\n\n\n\n## Document\n\n1. ApiMeter \u7528\u6237\u4f7f\u7528\u6587\u6863\uff1a[https://utils.git.umlife.net/apimeter](https://utils.git.umlife.net/apimeter/)\n2. ApiMeter PYPI\u53d1\u5e03\u7248\u672c\uff1a[https://pypi.org/project/apimeter](https://pypi.org/project/apimeter)\n\n## Usage\n```python\npip install apimeter  # \u5b89\u88c5\uff0c\u5b89\u88c5\u540e\u53ef\u7528\u5185\u7f6e\u547d\u4ee4\uff1aapimeter\u3001hrun\u3001apilocust\napimeter /path/to/api  # \u5b8c\u6574\u751f\u6210\u62a5\u544a\napimeter /path/to/api --skip-success  # \u62a5\u544a\u5ffd\u7565\u6210\u529f\u7528\u4f8b\u6570\n```\n\n\n## Development\n```python\n# \u672c\u5730\u5f00\u53d1\u4e0e\u8fd0\u884c\npoetry install  # \u62c9\u53d6\u4ee3\u7801\u540e\u5b89\u88c5\u4f9d\u8d56\npoetry run python -m apimeter /path/to/api  # \u5b8c\u6574\u751f\u6210\u62a5\u544a\npoetry run python -m apimeter /path/to/api --skip-success  # \u62a5\u544a\u5ffd\u7565\u6210\u529f\u7528\u4f8b\u6570\u636e\npython -m apimeter -h # \u67e5\u770b\u4f7f\u7528\u6307\u5357\n\n\n# \u6d4b\u8bd5\u8fd0\u884c\npython -m unittest discover # \u8fd0\u884c\u6240\u6709\u5355\u5143\u6d4b\u8bd5\npython -m unittest tests/test_context.py # \u8fd0\u884c\u6307\u5b9a\u6d4b\u8bd5\u6587\u4ef6\npython -m unittest tests.test_api.TestHttpRunner.test_validate_response_content # \u8fd0\u884c\u5355\u4e2a\u6d4b\u8bd5\u7528\u4f8b\n\npython -m tests.api_server \u6216 PYTHONPATH=. python tests/api_server.py # \u542f\u52a8\u6d4b\u8bd5\u793a\u4f8b\u670d\u52a1\u5668\npython -m apimeter tests/demo/demo.yml\npython -m apimeter tests/testcases --log-level debug --save-tests # \u6d4b\u8bd5\u793a\u4f8b\uff0c\u540c\u65f6\u8bbe\u7f6e\u65e5\u5fd7\u4e0e\u751f\u6210\u4e2d\u95f4\u5904\u7406\u6587\u4ef6\n\n\n# \u6253\u5305\u7f16\u8bd1\u4e0e\u53d1\u5e03\ngit tag v1.0.0 \u6216 git tag -a v1.0.0 -m \"\u53d1\u5e03\u6b63\u5f0f\u7248\u672c v1.0.0\" # \u6253\u6807\u7b7e\uff08\u8f7b\u91cf\u6216\u9644\u6ce8\uff09\ngit push v1.0.0 \u6216 git push --tags # \u63a8\u9001\u6807\u7b7e(\u5355\u4e2a\u6216\u6240\u6709)\npoetry build  # \u6253\u5305\npoetry publish  # \u53d1\u5e03\uff0c\u6839\u636e\u63d0\u793a\u8f93\u5165pypi\u8d26\u53f7\u5bc6\u7801\npip install -i https://pypi.Python.org/simple/ apimeter  # \u6307\u5b9a\u5b89\u88c5\u6e90\uff0c\u56e0\u4e3a\u521a\u53d1\u5e03\u5176\u4ed6\u5e73\u53f0\u672a\u53ca\u65f6\u540c\u6b65\n\n\n# \u6587\u6863\u7f16\u8bd1\u4e0e\u90e8\u7f72 .gitlab-ci.yml(\u8bbe\u7f6eapimeter-\u90e8\u7f72-Pages/\u5b8c\u5584\u529f\u80fd\u6587\u6863\uff0c\u66f4\u65b0mkdocs.yml\u914d\u7f6e)\npip install mkdocs-material==3.3.0\nmkdocs build\nmkdocs serve\n\n\n# \u9010\u884c\u4ee3\u7801\u8fd0\u884c\u65f6\u5185\u5b58\u5206\u6790\npoetry shell\npip install memory-profiler\n# 1. \u5bfc\u5165\u65b9\u5f0f\npython -m apimeter ~/Project/ATDD/tmp/demo_api/ --skip-success\n# 2. \u88c5\u9970\u5668\u65b9\u5f0f\npython -m memory_profiler apimeter ~/Project/ATDD/tmp/demo_api --skip-success --log-level error\n# 3. \u547d\u4ee4\u65b9\u5f0f\nmprof run apimeter /path/to/api\nmprof plot  # \u751f\u6210\u5185\u5b58\u8d8b\u52bf\u56fe\uff0c\u5b89\u88c5\u4f9d\u8d56pip install matplotlib\n# \u53c2\u8003\u94fe\u63a5\uff1ahttps://www.cnblogs.com/rgcLOVEyaya/p/RGC_LOVE_YAYA_603days_1.html\n```\n\n## Validate\n\n### 1\u3001\u6821\u9a8c\u5668\u652f\u6301\u4e24\u79cd\u683c\u5f0f\n```yaml\n- {\"check\": check_item, \"comparator\": comparator_name, \"expect\": expect_value}  # \u4e00\u822c\u683c\u5f0f\n- comparator_name: [check_item, expect_value]  # \u7b80\u5316\u683c\u5f0f\n```\n\n### 2\u3001\u652f\u6301\u81ea\u5b9a\u4e49\u6821\u9a8c\u5668\n\u5bf9\u4e8e\u81ea\u5b9a\u4e49\u7684\u6821\u9a8c\u51fd\u6570\uff0c\u9700\u8981\u9075\u5faa\u4e09\u4e2a\u89c4\u5219\uff1a\n- (1)\u81ea\u5b9a\u4e49\u6821\u9a8c\u51fd\u6570\u9700\u653e\u7f6e\u5230debugtalk.py\u4e2d;\n- (2)\u53c2\u6570\u6709\u4e24\u4e2a\uff1a\u7b2c\u4e00\u4e2a\u4e3a\u539f\u59cb\u6570\u636e\uff0c\u7b2c\u4e8c\u4e2a\u4e3a\u539f\u59cb\u6570\u636e\u7ecf\u8fc7\u8fd0\u7b97\u540e\u5f97\u5230\u7684\u9884\u671f\u7ed3\u679c\u503c;\n- (3)\u5728\u6821\u9a8c\u51fd\u6570\u4e2d\u901a\u8fc7assert\u5c06\u5b9e\u9645\u8fd0\u7b97\u7ed3\u679c\u4e0e\u9884\u671f\u7ed3\u679c\u503c\u8fdb\u884c\u6bd4\u8f83;\n```yaml\n# \u7528\u4f8b\n- test:\n    name: get token\n    request:\n        url: http://127.0.0.1:5000/api/get-token\n        method: GET\n    validate:\n        - {\"check\": \"status_code\", \"comparator\": \"eq\", \"expect\": 200}\n        - {\"check\": \"status_code\", \"comparator\": \"sum_status_code\", \"expect\": 2}\n\n# \u81ea\u5b9a\u4e49\u6821\u9a8c\u5668\ndef sum_status_code(status_code, expect_sum):\n    \"\"\" sum status code digits\n        e.g. 400 => 4, 201 => 3\n    \"\"\"\n    sum_value = 0\n    for digit in str(status_code):\n        sum_value += int(digit)\n    assert sum_value == expect_sum\n```\n\n### 3\u3001\u652f\u6301\u5728\u6821\u9a8c\u5668\u4e2d\u5f15\u7528\u53d8\u91cf\n\u5728\u7ed3\u679c\u6821\u9a8c\u5668validate\u4e2d\uff0ccheck\u548cexpect\u5747\u53ef\u5b9e\u73b0\u5b9e\u73b0\u53d8\u91cf\u7684\u5f15\u7528\uff1b\u800c\u5f15\u7528\u7684\u53d8\u91cf\uff0c\u53ef\u4ee5\u6765\u81ea\u56db\u79cd\u7c7b\u578b\uff1a\n- \uff081\uff09\u5f53\u524dtest\u4e2d\u5b9a\u4e49\u7684variables\uff0c\u4f8b\u5982expect_status_code\n- \uff082\uff09\u5f53\u524dtest\u4e2d\u63d0\u53d6\uff08extract\uff09\u7684\u7ed3\u679c\u53d8\u91cf\uff0c\u4f8b\u5982token\n- \uff083\uff09\u5f53\u524d\u6d4b\u8bd5\u7528\u4f8b\u96c6testset\u4e2d\uff0c\u5148\u524dtest\u4e2d\u63d0\u53d6\uff08extract\uff09\u7684\u7ed3\u679c\u53d8\u91cf\n- \uff084\uff09\u5f53\u524d\u6d4b\u8bd5\u7528\u4f8b\u96c6testset\u4e2d\uff0c\u5168\u5c40\u914d\u7f6econfig\u4e2d\u5b9a\u4e49\u7684\u53d8\u91cf\n```yaml\n- test:\n    name: get token\n    request:\n      url: http://127.0.0.1:5000/api/get_token\n      method: GET\n    variables:\n      - expect_status_code: 200\n      - token_len: 16\n    extract:\n      - token: content.token\n    validate:\n      - {\"check\": \"status_code\", \"comparator\": \"eq\", \u201cexpect\": \"$expect_status_code\"}\n      - {\"check\": \"content.token\", \"comparator\": \"len_eq\", \"expect\": \"$token_len\"}\n      - {\"check\": \"$token\", \"comparator\": \"len_eq\", \"expect\": \"$token_len\"}\n```\n\u57fa\u4e8e\u5f15\u7528\u53d8\u91cf\u7684\u7279\u6548\uff0c\u53ef\u5b9e\u73b0\u66f4\u7075\u6d3b\u7684\u81ea\u5b9a\u4e49\u51fd\u6570\u6821\u9a8c\u5668\n```yaml\n- test:\n    name: get token\n    request:\n        url: http://127.0.0.1:5000/api/get-token\n        method: GET\n    validate:\n        - {\"check\": \"status_code\", \"comparator\": \"eq\", \"expect\": 200}\n        - {\"check\": \"${sum_status_code(status_code)}\", \"comparator\": \"eq\", \"expect\": 2}\n\n# \u81ea\u5b9a\u4e49\u51fd\u6570\ndef sum_status_code(status_code):\n    \"\"\" sum status code digits\n        e.g. 400 => 4, 201 => 3\n    \"\"\"\n    sum_value = 0\n    for digit in str(status_code):\n        sum_value += int(digit)\n    return sum_value\n```\n\n### 4\u3001\u652f\u6301\u6b63\u5219\u8868\u8fbe\u5f0f\u63d0\u53d6\u7ed3\u679c\u6821\u9a8c\u5185\u5bb9\n\u5047\u8bbe\u63a5\u53e3\u7684\u54cd\u5e94\u7ed3\u679c\u5185\u5bb9\u4e3aLB123abcRB789\uff0c\u90a3\u4e48\u8981\u63d0\u53d6\u51faabc\u90e8\u5206\u8fdb\u884c\u6821\u9a8c\uff1a\n```yaml\n- test:\n    name: get token\n    request:\n        url: http://127.0.0.1:5000/api/get-token\n        method: GET\n    validate:\n        - {\"check\": \"LB123(.*)RB789\", \"comparator\": \"eq\", \"expect\": \"abc\"}\n```\n\n\n### 5\u3001\u5185\u7f6e\u6821\u9a8c\u5668\n| Comparator       | Description                    | A(check), B(expect)          | Examples                                     |\n|------------------|--------------------------------|------------------------------|----------------------------------------------|\n| eq               | value is equal                 | A == B                       | 9 eq 9                                       |\n| lt               | less than                      | A < B                        | 7 lt 8                                       |\n| le               | less than or equals            | A <= B                       | 7 le 8, 8 le 8                               |\n| gt               | greater than                   | A > B                        | 8 gt 7                                       |\n| ge               | greater than or equals         | A >= B                       | 8 ge 7, 8 ge 8                               |\n| ne               | not equals                     | A != B                       | 6 ne 9                                       |\n| str_eq           | string equals                  | str(A) == str(B)             | 123 str_eq '123'                             |\n| len_eq, count_eq | length or count equals         | len(A) == B                  | 'abc' len_eq 3, [1,2] len_eq 2               |\n| len_gt, count_gt | length greater than            | len(A) > B                   | 'abc' len_gt 2, [1,2,3] len_gt 2             |\n| len_ge, count_ge | length greater than or equals  | len(A) >= B                  | 'abc' len_ge 3, [1,2,3] len_ge 3             |\n| len_lt, count_lt | length less than               | len(A) < B                   | 'abc' len_lt 4, [1,2,3] len_lt 4             |\n| len_le, count_le | length less than or equals     | len(A) <= B                  | 'abc' len_le 3, [1,2,3] len_le 3             |\n| contains         | contains                       | [1, 2] contains 1            | 'abc' contains 'a', [1,2,3] len_lt 4         |\n| contained_by     | contained by                   | A in B                       | 'a' contained_by 'abc', 1 contained_by [1,2] |\n| type_match       | A is instance of B             | isinstance(A, B)             | 123 type_match 'int'                         |\n| regex_match      | regex matches                  | re.match(B, A)               | 'abcdef' regex 'a|w+d'                       |\n| startswith       | starts with                    | A.startswith(B) is True      | 'abc' startswith 'ab'                        |\n| endswith         | ends with                      | A.endswith(B) is True        | 'abc' endswith 'bc'                          |\n\n\n\n## \u6ce8\u610f\u4e8b\u9879\n```yaml\n# \u7528\u4f8bskip\u673a\u5236\uff0c\u652f\u6301\u7528\u4f8b\u5c42\u548cAPI\u5c42\n1. \u65e0\u6761\u4ef6\u8df3\u8fc7\uff1askip: skip this test unconditionally\n2. \u81ea\u5b9a\u4e49\u51fd\u6570\u8fd4\u56deTrue\uff1askipIf: ${skip_test_in_production_env()}\n3. \u81ea\u5b9a\u4e49\u51fd\u6570\u8fd4\u56deFalse\uff1askipUnless: ${skip_test_in_production_env()}\n\n# \u65e5\u5fd7\u8f93\u51fa\u9700\u8981\u6307\u5b9a\u7edd\u5bf9\u8def\u5f84\u6216\u76f8\u5bf9\u8def\u5f84\uff0c\u4e0d\u80fd\u6307\u5b9a\u5355\u72ec\u4e00\u4e2a\u6587\u4ef6\u540d\uff08\u6587\u4ef6\u53ef\u4ee5\u672a\u521b\u5efa\uff09\nhrun --log-level debug --log-file ./test.log   api/youcloud/query_product_api.yml\n\n# \u81ea\u5b9a\u4e49\u51fd\u6570\u4f7f\u7528\u4e86\u5b57\u5178\u53c2\u6570\uff0c\u9700\u8981\u4f7f\u7528\u53cc\u5f15\u53f7\u5305\u56f4\uff0c\u907f\u514dYAML\u89e3\u6790\u5668\u4f1a\u5c06\u5176\u8bef\u8ba4\u4e3a\u662f\u5b57\u5178\u5b9a\u4e49\u3002\u4f8b\u5982\uff1a\nsign: \"${get_sign_v3({device_sn: $device_sn, os_platform: $os_platform, app_version: $app_version})}\"\n\n# $\u8f6c\u4e49\n$$\n\n# \u4e00\u952e\u6253\u5305\u53d1\u5e03\uff0c\u66f4\u591a\u5185\u5bb9\u53c2\u8003 scripts\nmake release-patch  MESSAGE=\"\u652f\u6301\u81ea\u52a8\u5316\u6253\u5305\u53d1\u5e03\uff0c\u53d1\u5e03\u7248\u672cv2.8.4\" # \u81ea\u52a8\u7d2f\u79ef\u5c0f\u7248\u672c\nmake quick-release VERSION=2.85 MESSAGE=\"\u5b8c\u5584\u4f7f\u7528\u8bf4\u660e\u6587\u6863\uff0c\u53d1\u5e03\u7248\u672cv2.8.5\" # \u8df3\u8fc7\u5355\u5143\u6d4b\u8bd5\n```\n\n## \u9644\u5f55-\u76f8\u5173\u94fe\u63a5\n- HttpRunner: https://github.com/httprunner/\n- Requests: http://docs.python-requests.org/en/master/\n- unittest: https://docs.python.org/3/library/unittest.html\n- Locust: http://locust.io/\n- har2case: https://github.com/httprunner/har2case\n- HAR: http://httparchive.org/\n- Swagger: https://swagger.io/",
    "bugtrack_url": null,
    "license": "Apache-2.0",
    "summary": "One-stop solution for HTTP(S) testing.",
    "version": "2.10.0",
    "project_urls": {
        "Documentation": "https://docs.httprunner.org",
        "Homepage": "https://github.com/httprunner/httprunner",
        "Repository": "https://github.com/httprunner/httprunner"
    },
    "split_keywords": [
        "http",
        " api",
        " test",
        " requests",
        " locustio"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "bca01ffbf5c23963234da17590eacd75acf23943667f291559e8a1f2b1e9f47b",
                "md5": "df3181f8fdc4883ee3298638bf0d0a53",
                "sha256": "e8fbc3903dad25062b38a16c18e627b9fd99e86943173211d99ec0afad627d5b"
            },
            "downloads": -1,
            "filename": "apimeter-2.10.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "df3181f8fdc4883ee3298638bf0d0a53",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": "<4.0,>=3.6",
            "size": 84558,
            "upload_time": "2025-07-21T08:19:24",
            "upload_time_iso_8601": "2025-07-21T08:19:24.955783Z",
            "url": "https://files.pythonhosted.org/packages/bc/a0/1ffbf5c23963234da17590eacd75acf23943667f291559e8a1f2b1e9f47b/apimeter-2.10.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "44bb0b664984d231c29f203c0ddddd8431c8b87ace18fbed90426259a3d451e3",
                "md5": "839187c86b8e1f4f0e9ca3d3225cbede",
                "sha256": "652dd3a2c406a8e3142bd36d3a8d387fee87843fb7ce5ee1cd4da02ae68e5632"
            },
            "downloads": -1,
            "filename": "apimeter-2.10.0.tar.gz",
            "has_sig": false,
            "md5_digest": "839187c86b8e1f4f0e9ca3d3225cbede",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": "<4.0,>=3.6",
            "size": 76318,
            "upload_time": "2025-07-21T08:19:26",
            "upload_time_iso_8601": "2025-07-21T08:19:26.646731Z",
            "url": "https://files.pythonhosted.org/packages/44/bb/0b664984d231c29f203c0ddddd8431c8b87ace18fbed90426259a3d451e3/apimeter-2.10.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-07-21 08:19:26",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "httprunner",
    "github_project": "httprunner",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "apimeter"
}
        
Elapsed time: 2.09967s