# tester 概念、命名与设计理由 这是 tester 的**唯一术语真相**。写用例、改脚本、看文档遇到词义冲突时,以本文件为准。 ## 0. 三层结构 ``` catalog(整个测试集) └── category(= 一个 suite,一个 cases/.yaml) └── case(一条用例) ``` | 层 | 是什么 | 业界对应 | |---|---|---| | catalog | 全部用例 + 全局配置(`catalog.yaml`) | pytest 的一次 collection;整个 test suite | | category | 一组用例 + 自己的前置条件(`status_probe`) | Robot **suite**;pytest 一个目录 + conftest.py | | case | 一条用例 | test case | **「suite」只作总称**(泛指整个测试集),不作 YAML 键。集合级的角色由 **category** 承担:`cases/.yaml` 就是 suite 容器,`categories[].status_probe` 就是 Suite Setup。所以框架里没有 `suite` 字段。 ## 1. 术语表 每个词一句话定义 + 业界锚点 + 「它不是什么」。 ### 用例字段 | 词 | 定义 | 业界锚点 | 不是什么 | |---|---|---|---| | `case` | 一条可独立判定的测试 | test case | 不是一次运行记录(那是 `runs/`) | | `tag` | 成本档:`smoke` / `full` / `slow`,单选 | pytest `mark`;JUnit / Robot `tag` | 不是分类,不是「一个 suite」 | | `risk` | 执行副作用等级:`readonly` / `mutating` / `destructive` | read-only vs mutating;destructive tests;K8s e2e `[Disruptive]` | 不是概率,不是「要不要人」(那是 `automation`) | | `lifecycle` | 用例生命周期:`active` / `pending` / `retired` | JUnit `@Disabled`;pytest skip / deselect | 不是 HTTP 状态,不是前置探测(那是 `api-status` / `status_probe`) | | `driver` | 谁来驱动执行:`script` / `browser` / `mobile` | Selenium / Appium **driver** | 不是「要不要人」(那是 `automation`) | | `automation` | 人工参与度:`automated` / `assisted` / `manual` | TestRail *Automation Type*;ADO *Automation status*;Xray *Test Type* | 不是执行器(那是 `driver`) | | `session` | 可复用会话(浏览器 / 模拟器) | pytest fixture;Robot suite variable | 不是一条用例 | | `gate` | 项目细分闸门名 → 环境变量(`motion` / `hardware` / `weld`) | 授权开关 | 不是 `risk`,不要拿它代替风险等级 | | `authorize` | 风险档 → 环境变量。该档执行前必须为 `true` | pytest `skipif` 条件 | 不是认证(auth),是**授权** | | `execution_profile` | 执行环境策略:`standard` 或隔离可重置的 `isolated-full` | pytest marker/fixture policy | 不是风险等级;isolated-full 不会抹掉 `risk` | | `assume` | 额外跳过条件(gate / authorize 之外的环境表达式) | JUnit `assumeTrue`;pytest `skipif` | 不是授权开关的重复写法 | | `status_probe` | 分类级前置条件:请求一个 URL,命中即视为前置不满足 | pytest session fixture;Robot Suite Setup | 不是产品失败,不是整轮硬闸 | | `how` | 怎么测:`steps` / `run` / `url` / `timeout` | test steps | 不是判定标准 | | `expected` | 怎么算过:`{kind, value}` 列表 | pytest `assert`;Robot `Should …` | 不是步骤 | | `run_together` | 必须成组执行的用例(有序、可选 continue_on_fail) | 场景组(scenario) | 不是 suite,不是 tag | | `client_suite` | 客户端用户旅程套件:`client-smoke` / `client-regression` / `safety` | mobile/browser E2E suite | 不是 `category`,也不是通用 tag;迁移期可读 `suite` 别名 | | `test_type` | 顶层测试类型:`journey` 或 `integration` | E2E vs service/contract test | 不是 driver;旧目录可由既有字段推断 | | `integration_suite` | 集成套件:`api-contract` / `authorization` / `build-contract` | API/contract test suite | 不是客户端 `client_suite`;迁移期可读 `suite` 别名 | | `journey` | 用户目标、入口、操作、UI 断言、最终状态和失败继续策略 | Cucumber scenario / Robot task | 不是 API contract | | `fixture` | 旅程开始前声明的测试账号与隔离数据 | pytest fixture | 不是运行中临时猜出的数据 | | `cleanup` | 本轮数据的 teardown 契约;缺失会在 preflight 阻断 | pytest teardown / Robot teardown | 不是测试通过后的可选备注 | | `platform` | 旅程适用的 `android`/`ios` 平台列表 | device matrix | 不是 tester Agent binding;binding 仍在命令参数/运行环境 | ### 结果词 | 结果 | 定义 | 业界锚点 | |---|---|---| | `pass` | 通过 | passed | | `fail` | 产品缺陷 | failed | | `environment-fail` | 前置/环境导致无法执行,**永远不是产品失败** | pytest error;JUnit aborted | | `skipped` | **主动**不跑:standard 下的 `retired` / `pending` / 超风险上限 / 未授权 / `assume` 不成立 | pytest skipped;JUnit disabled | | `not-run` | **被动**没轮到:失败即停后剩下的;分类被 `status_probe` 挡住整组没执行;`automation` 需人工而无人值守 | pytest not run;deselected | ## 2. 命名迁移对照(v1 → v2) | v1 | v2 | 为什么改 | |---|---|---| | 用例 `status` | `lifecycle` | v1 的 `status` 一词三义:用例状态、`api-status` 的 HTTP 状态、`status_probe`。拆开后各是各的 | | 用例 `suite` | `tag` | Robot 里 suite 是**集合**,`cases/.yaml` 才是集合;我们却拿它当标记。改 `tag` 与 pytest mark / JUnit tag 对齐 | | 用例 `surface` | `driver` | `surface` 是自造词;`driver` 是 Selenium / Appium 的既有概念,且正好回答「谁来驱动」 | | 用例 `cleanup` | `teardown` | 与 pytest teardown / JUnit `@AfterEach` / Robot `[Teardown]` 对齐 | | 用例 `skip_unless` | `assume` | 语义是「假设成立才跑」,对应 JUnit `assumeTrue`;旧名与 `gate` 含义重叠 | | 用例 `requires` | **删除** | 只出现在导出的 CSV,不参与任何逻辑;126 条标注还不一致。前置能力改用 `tag` + `driver` + `gate`/`assume` 表达 | | catalog `auth_env` | `authorize` | `auth` 被读成「认证」,实际是「授权放行」 | | `risk` 值 `safe/act/harm` | `readonly/mutating/destructive` | `act` 太含糊(谁都在 act),`harm` 情绪化,且把「改了状态」与「难恢复」混成一维。新词是单调三级,见下 | | 结果 `skipped`(含被动) | `skipped` + `not-run` | v1 把「主动不跑」和「被动没轮到」混在一个词里,成绩单看不出是哪种 | ## 3. 关键设计理由(为什么是这些值) ### 3.1 `risk`:为什么是 `readonly / mutating / destructive` - 这一维回答的是「跑它会不会改变被测系统、有多难退回」,不是概率,也不是「要不要人」。 - `readonly` / `mutating` 是数据库、文件系统、API 测试里的通用二分,事实性描述。 - `destructive` 是硬件 / 内核 / 集群测试的标准词(Kubernetes e2e 用 `[Disruptive]` 标记会打断集群的用例)。 - 三级**单调**:`readonly ⊂ mutating ⊂ destructive`,所以只需要一个上限 `default_max_risk` 和一个 `--max-risk`,不用组合矩阵。 - 与 `gate` 正交:`risk` 说「影响多大」,`gate` 说「碰哪个子系统」。点动 vs 焊接这种细分授权用 `gate`,不要再造 risk 名。 - `tag=smoke` 只能配 `risk=readonly`:冒烟测试不许留下变化。 - 隔离可重置环境可设置 `execution_profile: isolated-full` 跑所有 active 用例;安全前置由 test-preflight capability 与 cleanup/rollback 契约保证,原始 risk 仍必须报告。 ### 3.2 `automation`:为什么是 `automated / assisted / manual` - 有些用例无法自动化,或需要人在现场配合(急停、断电、物理复位、目视 LED)。这需要在**选范围**阶段就能筛出来。 - 这是测试管理工具的标准字段(TestRail *Automation Type*、ADO *Automation status*、Xray *Test Type*),因此沿用 `automation` 这个名字。 - 与 `driver` 正交:`driver` 说「用什么驱动」,`automation` 说「要不要人」。把 `manual` 塞进 `driver` 就无法表达 `assisted`(它仍由 script/browser 驱动)。 - 三档的含义与约束: - `automated`(默认):Agent 端到端跑完。 - `assisted`:Agent 驱动,但至少一步需要人在场;该步在 `how.steps` 里以 `[人工]` 开头(可被 `validate` 机器判定)。 - `manual`:只能人工执行;禁止 `driver`、禁止 `how.run`,必须有 `how.steps`。 - 默认只跑 `automated`。无人值守时 `assisted` / `manual` 记 `not-run`;有人在场时 `assisted` 暂停等人确认,`manual` 由人工回报结果。 - isolated-full 只取消风险/授权过滤,不伪造 assisted/manual 的人工步骤。 ### 3.3 结果为什么拆出 `not-run` - `skipped` = **我们决定不跑**(retired / pending / 超上限 / 未授权 / assume 不成立)。 - `not-run` = **没轮到**(失败即停后剩下的 / 分类被前置挡住 / 需人工而无人值守)。 - 混在一起,成绩单就看不出「本轮到底跑没跑」。拆开后 `executed` 列(`yes` / `no`)与原因都清楚。 ### 3.4 `status_probe` 为什么是分类级 - 不同分类的前置条件不同:相机链路走 `API_BASE`(8095) / 相机内网,独立于机械臂 FSM(8096)。机械臂没连上不应该挡住相机测试。 - 因此它是**分类级前置条件**:`catalog.status_probe` 是全局默认,`categories[].status_probe` 可覆盖(缺省继承 / `false` 关闭 / mapping 整体替换)。对应 pytest 的 session fixture、Robot 的 Suite Setup。 - 命中或不可达只影响用它的分类;只有所有选中分类都被挡住,整轮结论才是 `environment-fail`。`select` 会按分类返回解析后的生效值,Agent 不要自己读 catalog 猜。 ### 3.5 客户端套件和生命周期 客户端套件固定为: - `client-smoke`:启动、正确账号登录和少量只读主路径,通常配 `tag=smoke`、`risk=readonly`。 - `client-regression`:完整的普通用户旅程,可包含隔离环境中的 `mutating` 操作;不等于所有 full 用例。 - `safety`:删除、权限边界、改密、停用或故障注入等危险旅程;必须显式选择和授权,不进入默认运行。 每条旅程按 `preflight → 启动 → 登录 → 旅程 → 断言 → 清理 → 报告` 执行。登录失败在 客户端和正确测试账号都已就绪时记 `fail`;设备、产物、服务或 session 不可达才记 `environment-fail`。fixture/cleanup 缺失是 contract block,不伪装成逐条结果。前置失败 后的剩余条目是 `not-run`,主动排除仍是 `skipped`。 ## 4. 不采用的词 | 词 | 不用它的原因 | |---|---| | `suite`(作字段) | 与 Robot 的「集合」语义冲突;集合角色已由 category 承担 | | `status`(作字段) | 与 HTTP 状态、前置探测撞词 | | `surface` | 自造词,业界无此概念 | | `requires` | 不参与逻辑,标注还容易失真 | | `sanity` / `regression` / `acceptance` | 与 `smoke` / `full` 分不清,且已有 category / id / risk / automation 多层筛选 | | `preconditions`(通用列表) | 当前只有 `status_probe` 一种前置,出现第二种再抽象 |