Skip to main content

接口测试平台测试用例执行引擎

Project description

api_engine_xin

轻量级 API 自动化测试执行引擎。支持 ${var} 变量替换、JSONPath 提取与断言、前后置 Python 脚本、多级前置接口递归、自定义函数注入、数据库访问、文件上传,以及套件级跨用例变量共享。

安装

pip install api_engine_xin

安装可选数据库驱动:

pip install api_engine_xin[all]    # SQL Server + Oracle + PostgreSQL
pip install api_engine_xin[sqlserver]

当前版本:0.0.28


使用场景

场景一:调试单条接口

最基本的用法——发送一个请求,提取响应数据,验证结果。

from ApiEngine.core import TestRunner

env = {
    "base_url": "http://api.example.com",
    "headers": {"Content-Type": "application/json"},
    "envs": {"token": "my-token-123"},
}

case = {
    "title": "查询用户信息",
    "interface": {"url": "/api/user/info", "method": "get"},
    "headers": {"Authorization": "Bearer ${token}"},
    "request": {"params": {"user_id": "1001"}},
    "setup_script": "",
    "teardown_script": "",
    "extract": [
        {"var_name": "username", "extract_expr": "$.data.username"},
    ],
    "assertions": [
        {"type": "相等", "field": "$.code", "expected": 200},
        {"type": "相等", "field": "status_code", "expected": 200},
    ],
}

runner = TestRunner(env)
result = runner.execute_cases(case)

print(result["status"])            # "success" | "fail" | "error"
print(result["response_body"])     # 响应体文本
print(result["log_data"])          # 执行日志列表

场景二:批量执行套件

多条用例打包为一个套件执行,返回汇总统计。

suite = {
    "name": "登录模块",
    "cases": [
        {
            "title": "登录",
            "interface": {"url": "/login", "method": "post"},
            "request": {"data": {"user": "admin", "pwd": "123"}},
            "setup_script": "", "teardown_script": "",
            "extract": [{"var_name": "token", "extract_expr": "$.token"}],
            "assertions": [{"type": "相等", "field": "$.code", "expected": 0}],
        },
        {
            "title": "查询个人信息",
            "interface": {"url": "/user/profile", "method": "get"},
            "headers": {"Token": "${token}"},
            "request": {},
            "setup_script": "", "teardown_script": "",
            "extract": [],
            "assertions": [{"type": "相等", "field": "$.code", "expected": 0}],
        },
    ],
}

result = runner.execute_cases(suite)
# result 是 list,每个元素是一个套件的汇总 dict:
# [{"name": "登录模块", "all": 2, "success": 2, "fail": 0, "error": 0, "cases": [...], "state": "success"}]

注意:不传 run_id 时,套件内各用例的变量不共享。如需跨用例传值,请用场景三。


场景三:跨用例变量共享(套件级执行)

用例 A 提取的 token,用例 B 要能直接用。通过 run_id 启用共享。

import time
from ApiEngine.core import TestRunner

env = {
    "base_url": "http://api.example.com",
    "envs": {"base_token": "initial"},
}

# 1. 注册一次运行
run_id = f"suite-1-{time.time()}"
TestRunner.register_run(run_id, initial_envs=env.get("envs", {}))

cases = [
    {
        "title": "登录获取 token",
        "interface": {"url": "/login", "method": "post"},
        "request": {"data": {"user": "admin", "pwd": "123"}},
        "setup_script": "", "teardown_script": "",
        "extract": [{"var_name": "token", "extract_expr": "$.data.token"}],
        "assertions": [],
    },
    {
        "title": "用 token 查询数据",
        "interface": {"url": "/data/list", "method": "get"},
        "headers": {"Authorization": "Bearer ${token}"},   # ← 用到上一条用例提取的 token
        "request": {},
        "setup_script": "", "teardown_script": "",
        "extract": [],
        "assertions": [{"type": "相等", "field": "$.code", "expected": 0}],
    },
]

try:
    for case in cases:
        runner = TestRunner(env, run_id=run_id)
        result = runner.execute_cases(case)
        print(f"{case['title']}: {result['status']}")
finally:
    # 2. 务必清理
    TestRunner.unregister_run(run_id)

配套类方法

方法 用途
register_run(run_id, initial_envs) 注册共享存储(套件开始前)
unregister_run(run_id) 清理共享存储(套件结束后,务必在 finally 中调用)
get_debug_updates(run_id) 获取 save_global_variable 产生的变更队列
clear_debug_updates(run_id) 重置变更队列(持久化到 DB 后调用)
get_run_globals(run_id) 获取当前累积的全部环境变量
cleanup_stale_runs(seconds) 兜底清理超时残留(默认 1 小时,按心跳判断不会误杀活跃套件)

场景四:前置接口(登录依赖链)

preconditions 定义前置步骤,引擎自动按深度优先执行。前置提取的变量主用例可直接用 ${var} 引用。

case = {
    "title": "创建订单",
    "interface": {"url": "/order/create", "method": "post"},
    "headers": {"Token": "${login_token}"},
    "request": {"json": {"product_id": "${product_id}", "qty": 1}},
    "setup_script": "",
    "teardown_script": "",
    "preconditions": [
        {
            "title": "登录",
            "interface": {"url": "/login", "method": "post"},
            "request": {"data": {"user": "admin", "pwd": "123"}},
            "setup_script": "", "teardown_script": "",
            "extract": [{"var_name": "login_token", "extract_expr": "$.data.token"}],
            "assertions": [{"type": "相等", "field": "$.code", "expected": 0}],
            "preconditions": [],
        },
        {
            "title": "查询商品",
            "interface": {"url": "/product/first", "method": "get"},
            "headers": {"Token": "${login_token}"},
            "request": {},
            "setup_script": "", "teardown_script": "",
            "extract": [{"var_name": "product_id", "extract_expr": "$.data.id"}],
            "assertions": [],
            "preconditions": [],
        },
    ],
    "extract": [],
    "assertions": [{"type": "相等", "field": "$.code", "expected": 0}],
}

runner = TestRunner(env)
result = runner.execute_cases(case)
# result["precondition_results"] 包含每个前置步骤的独立结果

前置支持无限嵌套(子前置的 preconditions 字段)。前置步骤与主用例共享 HTTP Session(Cookie 自动传递)。


场景五:自定义函数

通过 global_func 注入 Python 函数,在变量替换和脚本中调用。

tools_code = """
import random

def random_phone():
    return "138" + str(random.randint(10000000, 99999999))

def timestamp():
    import time
    return str(int(time.time()))
"""

env = {
    "base_url": "http://api.example.com",
    "envs": {
        "phone": "random_phone()",      # ← 字符串形式的函数调用
    },
    "global_func": tools_code,
}

case = {
    "title": "注册用户",
    "interface": {"url": "/register", "method": "post"},
    "request": {
        "json": {
            "mobile": "${phone}",              # → 调用 random_phone()
            "ts": "${timestamp()}",             # → 调用 timestamp()
        }
    },
    "setup_script": "", "teardown_script": "",
    "extract": [],
    "assertions": [],
}

替换优先级:临时变量(extract 提取的)→ 环境变量(envs)→ 函数调用。


场景六:前后置脚本

setup_script 在请求前执行,teardown_script 在请求后执行(可访问 response)。

case = {
    "title": "下单并验证",
    "interface": {"url": "/order/create", "method": "post"},
    "request": {"json": {"product": "A001"}},
    "setup_script": """
# 前置:生成随机备注
import random
note = "order_" + str(random.randint(1000, 9999))
test.save_env_variable("order_note", note)
""",
    "teardown_script": """
# 后置:从响应提取并做额外校验
body = response.json()
order_id = test.json_extract(body, "$.data.order_id")
test.save_global_variable("last_order_id", order_id)
test.assertion("相等", True, order_id is not None)
""",
    "extract": [],
    "assertions": [{"type": "相等", "field": "$.code", "expected": 0}],
}

脚本中可用对象

名称 说明
test BaseCase 实例,可调用 save_env_variablesave_global_variablejson_extractassertion
response HTTP 响应对象(仅 teardown)
ENV 环境 dict(含 base_urlenvs 等)
global_var envs 的直接引用
db 数据库客户端(见场景七)
print 输出到执行日志

场景七:数据库操作

配置 DB 连接后,在脚本中通过别名直接查询。

env = {
    "base_url": "http://api.example.com",
    "envs": {},
    "db": [
        {
            "name": "main_db",
            "type": "mysql",           # mysql | sqlserver | oracle | postgresql
            "config": {
                "host": "127.0.0.1",
                "port": 3306,
                "user": "root",
                "password": "123456",
                "database": "test_db",
            },
        }
    ],
}

case = {
    "title": "验证数据库记录",
    "interface": {"url": "/user/create", "method": "post"},
    "request": {"json": {"name": "test_user"}},
    "setup_script": "",
    "teardown_script": """
# 后置脚本查 DB 验证
row = db.main_db.execute_sql("SELECT * FROM users WHERE name=%s", ("test_user",))
print(f"DB 查询结果: {row}")
test.assertion("相等", "test_user", row["name"])
""",
    "extract": [],
    "assertions": [],
}

API

  • db.{name}.execute_sql(sql, params) → 返回单行(dict)
  • db.{name}.execute_all(sql, params) → 返回所有行(list[dict])

场景八:文件上传

case = {
    "title": "上传头像",
    "interface": {"url": "/upload/avatar", "method": "post"},
    "headers": {},
    "request": {
        "data": {"user_id": "1001"},
        "files": {
            "avatar": {"path": "/tmp/photo.png", "name": "photo.png"}
        },
    },
    "setup_script": "", "teardown_script": "",
    "extract": [],
    "assertions": [{"type": "相等", "field": "$.code", "expected": 0}],
}

支持三种格式:

# 单文件(路径 dict)
"files": {"field": {"path": "/abs/path.png", "name": "photo.png"}}

# 单文件(requests 原生元组)
"files": {"field": ("photo.png", open("/abs/path.png", "rb"))}

# 多文件
"files": {"field": [{"path": "/a.pdf"}, {"path": "/b.pdf"}]}

环境配置 test_env_data

字段 类型 必填 说明
base_url str 推荐 相对 URL 的前缀
headers dict 全局默认请求头(用例 headers 会覆盖同名字段)
envs dict 环境变量,支持 ${key} 替换
global_func str Python 源码,注入自定义函数
db dict | list 数据库连接配置

用例结构 case_payload

{
    "title": "用例名称",
    "interface": {
        "url": "/api/path",     # 相对路径自动拼接 base_url;绝对 URL 原样使用
        "method": "post",       # get | post | put | delete | patch
    },
    "headers": {},              # 与全局 headers 合并
    "request": {
        "params": {},           # query string
        "data": {},             # form body
        "json": {},             # json body
        "files": {},            # 文件上传
    },
    "preconditions": [],        # 前置接口列表
    "setup_script": "",         # 前置脚本
    "teardown_script": "",      # 后置脚本
    "extract": [],              # 数据提取
    "assertions": [],           # 断言
}

请求体选择:由 Content-Type 决定——application/jsonjsonform-urlencoded / multipartdata


变量替换

在 URL、headers、request body、assertions expected 中均可使用 ${var}

"headers": {"Authorization": "Bearer ${token}"}
"request": {"json": {"user_id": "${user_id}", "phone": "${random_phone()}"}}
"assertions": [{"type": "相等", "field": "$.name", "expected": "${expected_name}"}]

查找顺序:extract 提取的临时变量 → envs 环境变量 → 函数调用。


数据提取

"extract": [
    {"var_name": "token", "extract_expr": "$.data.token"},
    {"var_name": "first_id", "extract_expr": "$.data.list[0].id"},
]

提取后自动存入变量,后续用例(共享模式下)或当前用例的断言/脚本中可通过 ${token} 引用。


断言

"assertions": [
    {"type": "相等", "field": "$.code", "expected": 200},
    {"type": "相等", "field": "status_code", "expected": 200},
    {"type": "包含", "field": "$.message", "expected": "success"},
    {"type": "大于", "field": "$.data.total", "expected": 0},
]

field 支持 JSONPath 或特殊值 status_code(HTTP 状态码)。expected 支持 ${var}

类型 别名
相等 equals, eq, ==
相等忽略大小写 equals_ignore_case
不相等 not_equals, ne, !=
包含 / 不包含 contains / not_contains
大于 / 小于 / 大于等于 / 小于等于 gt / lt / ge / le
正则匹配 regex_match, regex

str 与 int/float 会自动尝试类型归一化("200" == 200)。


执行结果

单条用例

{
    "status": "success",        # success | fail | error
    "name": "用例标题",
    "url": "http://...",
    "method": "post",
    "status_code": 200,         # HTTP 状态码
    "response_body": "...",
    "response_headers": {...},
    "request_body": "...",
    "request_headers": {...},
    "run_time": "123 ms",
    "log_data": [...],          # 执行日志
    "extract_info": [...],      # 提取结果
    "assert_info": [...],       # 断言结果(含 passed 字段)
    "precondition_results": [...],  # 前置步骤结果
}

套件

{
    "name": "套件名称",
    "all": 10,
    "success": 8,
    "fail": 1,
    "error": 1,
    "cases": [...],
    "state": "fail",            # 全成功→success;有 fail→fail;否则→error
}

依赖

Python >=3.6
核心 requests>=2.26.0, pymysql>=1.0.0, jsonpath>=0.82
可选 pymssql(SQL Server), oracledb(Oracle), psycopg2-binary(PostgreSQL)

License

MIT — Author: Shawn

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

api_engine_xin-0.0.29.tar.gz (28.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

api_engine_xin-0.0.29-py3-none-any.whl (33.0 kB view details)

Uploaded Python 3

File details

Details for the file api_engine_xin-0.0.29.tar.gz.

File metadata

  • Download URL: api_engine_xin-0.0.29.tar.gz
  • Upload date:
  • Size: 28.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.5

File hashes

Hashes for api_engine_xin-0.0.29.tar.gz
Algorithm Hash digest
SHA256 38166ef463c276449ca73a9c9a4ed6d916c1b8f23cbc5bdd215280af6775b2bd
MD5 43df8678dfe2a5e08059a0a325fbe690
BLAKE2b-256 fa81899ab3eaa9c042fed981f4dcac73b6dae076a8d7da874f23d734c5091017

See more details on using hashes here.

File details

Details for the file api_engine_xin-0.0.29-py3-none-any.whl.

File metadata

File hashes

Hashes for api_engine_xin-0.0.29-py3-none-any.whl
Algorithm Hash digest
SHA256 a6eb35213de32d4fa910c0d002d8a5fe716ca37fb4bfb26ef2d814bdd86d25a1
MD5 726e58e9d00aaf085cd0788e8bc770e2
BLAKE2b-256 54f3b61ff269f27795a7f90f6a44522a046bed5ada22192e49c1b485e74dc425

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page