pytest-testkit
pytest用例环境注册和日志记录插件
安装
从仓库发行版中下载whl包
wget https://gitcode.com/openlibing/openlibing-pytest-executor/releases/download/v1.0.0-alpha/pytest_testkit-1.0.0-py3-none-any.whl
安装
pip install --force-reinstall pytest_testkit-1.0.0-py3-none-any.whl
本地调试测试用例执行
安装完成后,pytest 会自动使用插件。
测试仓库下生成 testbed 模版:
pytest --collect-only --outtestbed=testbed.json
生成的 testbed.json 举例如下:
{
"devices": [
{
"template_type": "multi_device_cover_single_type",
"env_definition": [
{
"name": "device_1",
"ip": "",
"port": 22,
"user": "",
"pwd": ""
},
{
"name": "device_2",
"ip": "",
"port": 22,
"user": "",
"pwd": ""
}
]
}
]
}
修改 testbed.json 中设备信息后,执行下面命令即可识别环境信息,执行测试用例:
pytest --testbed=testbed.json testcase/test_demo.py -k test_single_device_get_npu_info -s
在 pytest 中,你可以通过在命令行中使用 :: 符号来精确指定要执行的测试文件、测试类或单个测试方法,如: 文件名::类名::方法名 。
执行后,可以在 根目录/logs/TestCases 下查看用例的html格式日志
💡 提示:如果想要调试 环境调度直接分配的是容器场景的话,可以在环境上拉起容器时设置映射的端口,这样本地调试就可以直接连接上容器,模拟直接 在容器内执行命令的场景, 映射的命令:
docker run -d --name <name> -p 2222:22 <image>:<tag>
logger 使用
导入 logger:
from pytest_testkit.lib.common.log import logger
使用 logger 记录日志:
logger.info(f"[Device._login] Successfully connected to device")
日志目录结构
执行测试后,日志文件会按以下结构组织:
logs/
└── TestCases/
└── 2026-06-02/ # 日期目录(YYYY-MM-DD)
└── test_npu_info_11-38-05/ # 用例名_时间目录(HH-MM-SS)
└── test_npu_info.html # HTML 报告文件
目录结构说明:
- TestCases: 测试用例日志根目录
- 日期目录: 按执行日期组织(格式:
YYYY-MM-DD) - 用例子目录: 按用例名和执行时间命名(格式:
用例名_HH-MM-SS) - HTML 文件: 插件生成的测试报告,文件名与用例名一致
通过 logger.path 属性可以获取当前用例的日志归档目录路径,方便在测试用例中创建自定义文件。
日志级别:
| 级别 | 值 | 方法 | 说明 |
|---|---|---|---|
| TRACE | 5 | logger.trace() |
跟踪信息(最小级别) |
| INFO | 20 | logger.info() |
一般信息 |
| TC_START | 21 | logger.tc_start() |
测试用例开始 |
| TC_STEP | 22 | logger.tc_step() |
测试步骤记录 |
| PRE_SET | 23 | logger.pre_set() |
前置设置 |
| POST_SET | 24 | logger.post_set() |
后置设置 |
| CMD | 25 | logger.cmd() |
命令日志 |
| CMD_RESPONSE | 26 | logger.cmd_response() |
命令响应日志 |
| TC_END | 27 | logger.tc_end() |
测试用例结束 |
| WARN | 30 | logger.warning() |
警告信息 |
| FAIL | 35 | logger.fail() |
测试失败 |
| ERROR | 40 | logger.error() |
错误信息 |
logger的默认输出目录为logs,级别默认为INFO,如果需要配置:
1、在pytest.ini中配置(推荐做法)
infra_log_dir = logs
infra_log_level = INFO
2、在执行命令时指定
pytest --testbed=testbed.json --infra_log_level=INFO ./testcase/test_demo.py::TestDemoExecution::test_single_device
用例环境模板使用
基本用法
包含一个json文件和两个py文件
# resource/device_desc/template/single_device_template.json
{
"template_type": "single_device",
"env_definition": {
"nodes_1": [
{
"name": "device",
"pool": "default",
"label": "server"
}
]
}
}
# resource/mapper.py
from typing import Dict
ENV_TEMPLATES: Dict[str, str] = {
"single_device": "resource/device_desc/template/single_device_template.json",
}
# test_case.py
from resource.mapper import ENV_TEMPLATES
@pytest.mark.env(template=ENV_TEMPLATES["single_device"])
def test_custom_attrs():
assert True
模板结构说明
device_template/
├── template_type #模版类型,模本的唯一性标识,请保证不同模版间值唯一
├── env_definition #环境定义列表
│ ├── nodes_1 #第一套设备组合,代表用例需要覆盖的一种硬件形态,多套设备组合代表用例要覆盖多种硬件形态,会执行多次
├── device1 #设备1,设备详情字段见下表
├── device2 #设备2
│ ├── nodes_2 #第二套设备组合,一套设备组合中的设备必须是同一种类型(都为容器或都为裸机)
├── device
│ ├── ...
容器类型device字段说明:
| 字段名 | 含义 | 示例 |
|---|---|---|
| name | 设备名称标识,必填 | "device_1" |
| res_type | 资源类型,标识为容器,必填 | 固定值:"container" |
| cpu_arch | CPU架构类型,非必填,默认值:ARM | 可选值:"ARM"、"X86" |
| cpu_core_num | CPU核数,非必填,默认值:1 | 1 |
| npu_gen | NPU代际版本,非必填,不填则此项无要求 | "A2" |
| npu_model | NPU具体型号,非必填,不填则此项无要求 | "Ascend 910B4" |
| npu_num | NPU数量,非必填,不填则无NPU | 1 |
| memory_size | 内存大小,单位GB,非必填,默认值2 | 2 |
| storage_opt_size | 容器存储大小,单位GB,默认值40 | 40 |
| softwares | 软件列表,非必填,name: 软件名称,version:软件版本,config: 安装配置 | [{"name":"cann","version":"8.0.T16","config":""}] |
| image_label | 容器镜像,如果使用日构建镜像,此处可不填,由CI透传 | "cann8.5.0-py3.11-torch2.9.0-torch_npu2.9.0rc1" |
| command | 容器启动命令,非必填 | ["/bin/bash", "-c", "python app.py"] |
| mount_source_data | 需要挂载的共享存储数据源,非必填 | ["deepseek_v4_pro", "baai_taco"] |
| mount_dest_dir | 共享存储挂载的目的路径,按照索引和数据源对应,非必填 | ["/mnt/ascend/models/deepseek_v4_pro", "/mnt/ascend/dataset/baai_taco"] |
说明:
1、软件安装:需要业务自己提前写好安装脚本,提供给HidevLab归档后,申请环境时才可以安装
2、共享存储挂载:数据需要提前在共享存储服务器维护,并在HidevLab配置数据和存储路径的映射关系
容器类型环境模板示例:
{
"name": "device_1",
"res_type": "container",
"cpu_arch": "ARM",
"cpu_core_num": 1,
"npu_gen": "A2",
"npu_model": "Ascend 910B4",
"npu_num": 1,
"memory_size": 32,
"storage_opt_size": 40,
"softwares": [
{
"name": "cann",
"version": "8.0.T16",
"config": ""
}
],
"image_label": "swr.cn-north-4.myhuaweicloud.com/hw-test-images/ubuntu-310-sshd:latest",
"command": ["/bin/bash", "-c","python app.py"],
"mount_source_data": ["deepseek_v4_pro", "baai_taco"],
"mount_dest_dir": ["/mnt/ascend/models/deepseek_v4_pro", "/mnt/ascend/dataset/baai_taco"]
}
裸机类型device字段说明:
| 字段名 | 含义 | 示例 |
|---|---|---|
| name | 设备名称标识 | "device_2" |
| res_type | 资源类型,标识为物理机,必填 | 固定值:"bms" |
| device_model | 物理服务器设备型号,非必填,不填则此项无要求 | "TS200 2280H V2" |
| cpu_arch | CPU架构类型,非必填,默认值:ARM | 可选值:"ARM"、"X86" |
| cpu_core_min_num | 最小CPU核心数量要求,非必填,不填则此项无要求 | 1 |
| npu_gen | NPU代际版本,非必填,不填则此项无要求 | "A2" |
| npu_model | NPU具体型号,非必填,不填则此项无要求 | "Ascend 910B4" |
| npu_min_num | 最小NPU数量要求,非必填,不填则此项无要求 | 1 |
| memory_min_size | 最小内存容量(GB),非必填,不填则此项无要求 | 16 |
| storage_min_size | 最小存储容量(GB),非必填,不填则此项无要求 | 300 |
| softwares | 软件列表,非必填,name: 软件名称,version:软件版本,config: 安装配置 | [{"name":"cann","version":"8.0.T16","config":""}] |
| os_release | 操作系统版本信息,非必填,不填则此项无要求 | CentOS 7.6 |
| mount_source_data | 需要挂载的共享存储数据源,非必填 | ["deepseek_v4_pro", "baai_taco"] |
| mount_dest_dir | 共享存储挂载的目的路径,按照索引和数据源对应,非必填 | ["/mnt/ascend/models/deepseek_v4_pro", "/mnt/ascend/dataset/baai_taco"] |
说明:
1、软件安装:需要业务自己提前写好安装脚本,提供给HidevLab归档后,申请环境时才可以安装
2、共享存储挂载:数据需要提前在共享存储服务器维护,并在HidevLab配置数据和存储路径的映射关系
裸机类型环境模板示例:
{
"name": "device_1",
"res_type": "bms",
"device_model": "TS200 2280H V2",
"cpu_arch": "ARM",
"cpu_core_min_num": 1,
"npu_gen": "A2",
"npu_model": "Ascend 910B4",
"npu_min_num": 1,
"memory_min_size": 16,
"storage_min_size": 300,
"softwares": [
{
"name": "cann",
"version": "8.0.T16",
"config": ""
}
],
"os_release": "CentOS 7.6",
"mount_source_data": ["deepseek_v4_pro", "baai_taco"],
"mount_dest_dir": ["/mnt/ascend/models/deepseek_v4_pro", "/mnt/ascend/dataset/baai_taco"]
}
case_info 标记使用
pytest-testkit 插件支持通过 case_info 标记为测试用例添加自定义属性,用于用例分类和筛选。
基本用法
# 支持任意自定义属性
@pytest.mark.case_info(
level="P1", type="Performance", owner="team1", module="auth", tags="perf"
)
def test_custom_attrs():
assert True
用例筛选
在 pytest.ini 中配置筛选条件,只运行匹配的用例:
[pytest]
# 筛选 level=P1 且 module=auth 的用例
[case_info]
level = P1
module = auth
筛选逻辑说明:
- 精确匹配:ini 中配置的属性必须与用例属性精确匹配
- 通配:ini 中未配置的属性不参与筛选(匹配所有)
示例:
# 只筛选 level=P1 的用例
[case_info]
level = P1
# 筛选 level=P1 且 owner=team1 的用例
[case_info]
level = P1
owner = team1
error_log_fail 功能
pytest-testkit 插件支持当测试执行过程中出现 ERROR/WARN/FAIL 等级别日志时自动标记测试失败。
支持CLI 参数 和 ini 配置,默认为禁用
使用方式:
1、命令行参数(推荐用于临时启用)
pytest --error-log-fail --testbed=testbed.json testcase/
2、pytest.ini 配置(推荐用于项目级永久配置)
[pytest]
error_log_fail = true
使用示例:
# 开启error_log_fail功能后 用例结果为fail
def test_environment(logger):
logger.error("this is a error log")
assert True
quiet_stdout 功能
pytest-testkit 插件支持全局静默 sendcmd / sendcmd_interactive 的 stdout 输出,避免大量回显淹没日志。
开启后,stdout 内容不会出现在日志中,即使调用方显式传 echo_output=True 也会被全局压制。
exit_code、prompt、stderr 等信息不受影响。
支持CLI 参数 和 ini 配置,默认为禁用
使用方式:
1、命令行参数(推荐用于临时启用)
pytest --quiet-stdout --testbed=testbed.json testcase/
2、pytest.ini 配置(推荐用于项目级永久配置)
[pytest]
quiet_stdout = true
CI 运行元数据(ci_env_info)
执行器(pytest-executor)在调度用例时,会把 CI 流水线的运行元数据序列化成单个
ci_env_info 环境变量注入用例进程,覆盖 runner / remote / local 三种执行模式。
用例侧统一通过读取该环境变量获取当前流水线上下文,无需关心运行在哪种模式。
字段说明
环境变量 ci_env_info 的值是一个 JSON 字符串,固定包含以下白名单字段(值均为字符串):
| 字段名 | 含义 | 示例 |
|---|---|---|
| project_name | CI 项目名称 | "openlibing/demo" |
| workflow_name | 工作流名称 | "nightly" |
| job_name | 任务(job)名称 | "run-pytest" |
| run_number | 流水线运行序号 | "128" |
| step_name | 当前步骤名称 | "pytest-orch" |
读取方式
import json
import os
def test_use_ci_metadata():
raw = os.environ.get("ci_env_info", "")
info = json.loads(raw) if raw else {}
job_name = info.get("job_name", "")
run_number = info.get("run_number", "")
# 例如按流水线序号归档产物、按 job 名分支断言逻辑
assert isinstance(job_name, str)
取值约定
- 白名单字段恒在:无论入参是否提供,解析后的 JSON 一定包含上表 5 个字段,缺省值为
""。 - 值经过清洗:单个字段值会剔除 NUL、换行等控制字符,并按 128 字符截断;
非字符串标量(如数字)转为字符串,非标量(字典/列表)降级为
""。 - 非法输入不阻断用例:入参结构不合法、缺失或类型错误时一律降级为空串并告警,
不会中断用例调度;未注入时
ci_env_info为""。 - 纯 ASCII 安全:JSON 以
ensure_ascii序列化,含中文等非 ASCII 值时以\uXXXX转义,避免经 SSH 传输后乱码。 - 该变量由执行器注入,本地直接
pytest调试时通常为空,用例代码应对空值做好兜底。
支持的 fixture
-
logger: 提供日志对象
- info, warning, error等方法: 记录不同级别的日志
msg: 日志消息字符串
日志记录示例:
def test_environment(logger): logger.info("this is a info log") logger.warning("this is a waring log") logger.error("this is a error log")- set_header, set_footer: 设置 HTML 报告头部和尾部内容
html_content: 要注入的 HTML 内容append: 是否追加到现有内容之后,默认覆盖
HTML 内容注入示例:
def test_example(logger): # 设置 HTML 报告头部内容(注入位置:统计信息头之后、详细信息表格之前) logger.set_header('<span class="custom-header">算子测试结果</span>') # 设置 HTML 报告尾部内容(注入位置:详细信息表格之后) logger.set_footer('<span class="custom-footer">测试完成</span>') # 多次注入内容默认覆盖,可通过 append=True 追加内容 logger.set_header("<span>第一部分</span>", append=True) logger.set_header("<span>第二部分</span>", append=True) # 结果:第一部分 + 第二部分- path: 获取当前用例的日志归档目录路径(只读属性)
- 返回值: 当前用例的日志归档目录路径字符串,若未进入用例则返回
None
- 返回值: 当前用例的日志归档目录路径字符串,若未进入用例则返回
获取日志目录路径示例:
def test_example(logger): # 获取当前用例的日志归档目录路径 log_dir = logger.path if log_dir: print(f"当前用例日志目录: {log_dir}") # 可以在此目录下创建自定义文件 custom_file = os.path.join(log_dir, "custom_report.txt") with open(custom_file, "w") as f: f.write("自定义报告内容") - info, warning, error等方法: 记录不同级别的日志
-
environment/environments: 提供单设备/多设备对象
-
sendcmd(cmd, timeout=None, env_vars=None, cwd=None, ignore_err=False, only_stdout=True):执行命令(非交互式)
cmd: 要执行的命令字符串timeout: 命令执行超时时间(秒)env_vars: 环境变量字典cwd: 工作目录,命令将在该目录下执行(可选)ignore_err: 是否忽略命令执行错误,默认 False。设为 True 时即使命令失败 success 也为 Trueonly_stdout: 为 True 时只返回 stdout 字符串,为 False 时返回完整字典- 返回值: 默认返回 stdout 字符串;当
only_stdout=False时返回{'success': bool, 'stdout': str, 'stderr': str}
-
sendcmd_interactive(cmd, expect_prompt=None, timeout=600, cwd=None, ignore_err=False, only_stdout=True):执行命令(交互式)
cmd: 要执行的命令字符串expect_prompt: 期望匹配的提示符正则表达式(可选)timeout: 命令执行超时时间(秒)。默认值600scwd: 工作目录,命令将在该目录下执行(可选)ignore_err: 是否忽略提示符匹配失败,默认 False。设为 True 时即使未匹配到提示符 success 也为 Trueonly_stdout: 为 True 时只返回 stdout 字符串,为 False 时返回完整字典- 返回值: 默认返回 stdout 字符串;当
only_stdout=False时返回{'success': bool, 'stdout': str, 'stderr': str} - 输出清理: 返回的 stdout 会自动清理以下内容,便于用户直接使用:
- CSI 控制序列(如
\x1b[32m颜色代码、\x1b[?2004hbracketed paste mode 等) - OSC 控制序列(如
\x1b]0;title\x07窗口标题设置等) - 命令回显及其之前的内容(包括 Login Banner)
- 回车换行符统一转换为
\n(\r\n、\r自动处理)
- CSI 控制序列(如
-
upload_file(local_file, remote_file):上传文件
local_file: 本地文件路径remote_file: 远程文件路径- 返回值: 上传成功返回True,失败返回False
-
download_file(remote_file, local_file):下载文件
remote_file: 远程文件路径local_file: 本地文件路径- 返回值: 下载成功返回True,失败返回False
-
reconnect():设备重连
- 返回值: 重连成功返回True,失败返回False
设备参数使用方法示例如下:
# 单设备 def test_environment(environment): my_env_dict = {"MY_VAR": "value"} output = environment.sendcmd("ls", timeout=5, env_vars=my_env_dict) print(output) # 直接打印stdout字符串 # 需要获取完整结果时 result = environment.sendcmd("ls", only_stdout=False) assert result["success"] print(result["stdout"]) # 在指定目录执行命令 output = environment.sendcmd("pwd", cwd="/tmp") # 忽略命令执行错误并获取完整结果 result = environment.sendcmd("some_command", ignore_err=True, only_stdout=False) # result['success'] 总是 True environment.upload_file("local.txt", "/remote/path/remote.txt") environment.download_file("/remote/path/remote.txt", "local.txt") # 多设备 device1_name为模板中name 其他用法与单设备相同 参考单设备示例 def test_environments(environments): environment_device1 = environments["device1_name"] output = environment_device1.sendcmd("ls") # 需要检查执行结果时 result = environment_device1.sendcmd("ls", only_stdout=False) if not result["success"]: print(f"Error: {result['stderr']}") -
-
set_docker(docker_name_or_id):注册 Docker 容器(名称或 ID)
docker_name_or_id: 容器名称或容器 ID(Docker ID 为小写十六进制字符串)- 返回值: None
- 功能: 注册设备上的 Docker 容器,注册后可通过
__getitem__获取容器代理对象 - 查询顺序: 先尝试按 ID 查询,若找不到再尝试按名称精确查询
-
DockerProxy 容器代理对象:通过
environment["container_name"]获取-
代理对象提供与 Device 一致的接口,自动包装
docker exec命令 -
sendcmd(cmd, timeout=None, ignore_err=False, only_stdout=True):非交互式命令
- 每次独立执行
docker exec {docker_name} {cmd} - 返回值: stdout 字符串或完整字典
- 每次独立执行
-
sendcmd_interactive(cmd, expect_prompt=None, timeout=600, cwd=None, ignore_err=False, only_stdout=True):交互式命令
- 持久 session 模式:首次调用进入容器,后续调用在同一 session 执行
- 只有交互式 shell 命令(bash/sh/python/mysql 等)才创建持久 session
- 非交互式命令(cat/ls/grep 等)执行后自动退出,不设置 session 状态
- 参数与 Device.sendcmd_interactive 相同
- 返回值: stdout 字符串或完整字典
-
exit_docker(expect_prompt=None, timeout=3):退出容器 session
- 执行
exit命令退出容器,回到主机 shell - 若未进入 session 状态,打印 warning 日志并返回空字符串
- 执行
Docker 容器代理使用示例:
# 单设备场景 def test_with_docker(environment): # 在裸机上拉起容器 environment.sendcmd("docker run -d --name my_container ubuntu:latest sleep 3600") # 注册容器 environment.set_docker("my_container") # 获取容器代理对象 container = environment["my_container"] # 非交互式命令(每次独立执行) output = container.sendcmd("ls /") assert "bin" in output # 进入容器交互式 session # 必须使用/bin/bash 、/bin/sh 等交互式 shell 命令才能进入交互式 session container.sendcmd_interactive("/bin/bash") # 在同一 session 中执行多条命令 container.sendcmd_interactive("ls -la") container.sendcmd_interactive("cat /etc/hosts") # 退出容器交互式 session 非交互式无需调用 container.exit_docker() # environment.sendcmd 仍执行裸机命令 environment.sendcmd("docker ps") # 多设备场景 def test_multi_device_with_docker(environments): dev1 = environments["device1"] # 在 device1 上拉起容器 dev1.sendcmd("docker run -d --name app_container nginx:latest") # 注册容器 dev1.set_docker("app_container") # 获取容器代理对象(两级字典取值) container = dev1["app_container"] output = container.sendcmd("nginx -v") # 非 shell 命令不会创建交互式 session container.sendcmd_interactive("cat /root/abc.txt") container.exit_docker() # 打印 warning,不执行 exit # 通过容器 ID 注册(适合容器无名称的情况) def test_with_docker_by_id(environment): # 在裸机上拉起容器(不指定名称) environment.sendcmd("docker run -d ubuntu:latest sleep 3600") # 获取容器 ID output = environment.sendcmd("docker ps -q --filter ancestor=ubuntu:latest") container_id = output.strip() # 例如 "a1b2c3d4e5f67890..." # 注册容器(通过 ID,支持短 ID 或完整 ID) # Docker ID 为小写十六进制字符串(至少需要能唯一标识容器) environment.set_docker(container_id[:12]) # 使用短 ID(12 位) # 或使用完整 ID: environment.set_docker(container_id) # 获取容器代理对象 container = environment[container_id[:12]] # 执行命令 output = container.sendcmd("ls /") assert "bin" in output # 清理容器 environment.sendcmd(f"docker stop {container_id}") environment.sendcmd(f"docker rm {container_id}")注意:
- 交互式 shell 命令集合:bash、sh、ash、dash、zsh、python、python3、mysql、psql 等
- 非交互式命令(cat/ls/grep 等)使用
sendcmd_interactive后执行完毕自动退出容器,不会设置 session 状态 - 进入交互式shell后需调用
exit_docker()退出容器 session,非交互式无需调用 set_docker支持容器名称和容器 ID,查询顺序:先尝试 ID 查询,失败后再尝试名称精确查询
-
前置动作注册
支持在测试执行前注册并执行前置动作,用于环境变量加载、参数注入、设备初始化等场景。
核心特性:
- 在
conftest.py的pytest_runtest_setup中通过register_pre_action注册前置动作 pytest_runtest_setup在每个用例执行前都会被调用- 前置动作在环境分配后、每个测试用例执行前自动执行
- 执行失败时用例直接报错,报错信息包含失败动作名称和详细错误
使用方式:
在 conftest.py 中定义并注册前置动作:
import pytest
from pytest_testkit import (
register_pre_action,
clear_pre_action_registry
)
def load_env_var(environments):
"""在远程设备上设置环境变量"""
device = list(environments.values())[0]
result = device.sendcmd_interactive("export TEST_MODE=MY_ENV", timeout=5)
return "set succ"
def wait_seconds(delay):
"""本地等待(第一个参数不是 environments,不注入设备)"""
import time
time.sleep(delay)
return {"waited": delay}
@pytest.hookimpl(hookwrapper=True) # 装饰器必选
def pytest_runtest_setup(item):
# 可选 执行前置动作前清空历史注册的前置动作
clear_pre_action_registry()
# load_env_var函数第一个参数是 environments,注入设备字典
register_pre_action(
name="load_env_var",
func=load_env_var,
)
# wait_seconds函数第一个参数不是 environments,不注入设备字典
register_pre_action(
name="wait_seconds",
func=wait_seconds,
args=[2]
)
# 必选 需放在register_pre_action后,可理解为调用插件的pytest_runtest_setup函数,执行前置动作
yield
多个conftest.py调用顺序:
假设有两组用例分别在TestRepo/tests和TestRepo/unittest下,
conftest.py可选位置及pytest_runtest_setup的调用顺序举例如下
# 目录结构
TestRepo
├── conftest.py
├── tests/
│ └── conftest.py
└── unittest/
└── conftest.py
# 以tests/下用例某个用例为例,
# pytest_runtest_setup先调用用例同级的conftest.py文件,再调用父级目录下的
TestRepo/tests/conftest.py
↓
TestRepo/conftest.py
register_pre_action 接口参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
name |
str | 前置动作唯一标识 |
func |
Callable | 执行函数 |
args |
list | 传递给 func 的位置参数 |
kwargs |
dict | 传递给 func 的关键字参数 |
forced |
bool | 是否强制执行。默认为 False,相同name的前置动作在整个 session 中只执行一次;设置为 True 时,即使已执行过也会在每个测试用例前重新执行 |
函数签名规则:
系统通过检测函数第一个参数名判断是否注入 environments:
- 第一个参数是
environments:系统自动注入设备字典,可使用environment提供的方法操作环境,如sendcmd
def my_action(environments, **kwargs):
for name, device in environments.items():
device.sendcmd("hostname")
register_pre_action(name="my_action", func=my_action)
- 第一个参数不是
environments:不注入设备字典
def local_action(delay, message):
import time
time.sleep(delay)
print(message)
return {"done": True}
register_pre_action(name="local_action", func=local_action, args=[1, "hello"])
错误处理:
- Registry 未初始化:抛出
RegistryNotInitializedError - 执行失败:抛出
PreActionError,测试用例报错终止
执行测试
基本命令
# 指定 testbed 文件执行测试
pytest --testbed=./testbed.json testcase/
# 指定测试目录
pytest --testbed=./testbed.json tests/
# 运行指定测试
pytest --testbed=./testbed.json testcase/test_demo.py::TestDemoExecution::test_single_device
# 过滤测试用例(按 level 和 type)
pytest --testbed=./testbed.json testcase/ -v
# 生成 testbed 模板
pytest --collect-only --outtestbed=testbed.json
pytest.ini 配置
[pytest]
# 日志配置:目录、级别
infra_log_dir = logs
infra_log_level = INFO
# 错误日志失败配置:当检测到 ERROR/WARN/FAIL 日志时标记测试失败
error_log_fail = true
# 指定测试文件搜索路径(可提升收集速度,避免扫描无关目录)
testpaths = tests/demo
# 默认命令行选项(每次运行 pytest 时自动应用)
addopts =
-v # 详细输出
--tb=short # 精简 traceback
-m smoke # 按自定义mark过滤
# 注册自定义 markers(避免使用未声明的 marker 产生警告)
markers =
smoke: 标记冒烟测试用例
ui: UI 自动化测试
api: 接口测试
# 忽略某些文件或目录(可选)
; ignore =
; tests/old/
; tests/conftest_legacy.py
# 指定 Python 文件匹配模式
python_files = test_*.py
# 指定测试类和函数的命名规则
python_classes = Test*
python_functions = test_*
# 自定义用例筛选条件,对应用例的自定义mark中的case_info信息
[case_info]
level = P0
type = Functional
# 用例id列表筛选条件,使用用例函数名筛选,多个用例函数名使用英文逗号隔开不要带空格
[test_ids]
include = id1,id2
exclude = id3,id4
Dependencies
见requirements.txt
License
MIT License