Files
.pouch/skills/tester/references/concepts.md
T

136 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tester 概念、命名与设计理由
这是 tester 的**唯一术语真相**。写用例、改脚本、看文档遇到词义冲突时,以本文件为准。
## 0. 三层结构
```
catalog(整个测试集)
└── category(= 一个 suite,一个 cases/<cat>.yaml)
└── case(一条用例)
```
| 层 | 是什么 | 业界对应 |
|---|---|---|
| catalog | 全部用例 + 全局配置(`catalog.yaml`) | pytest 的一次 collection;整个 test suite |
| category | 一组用例 + 自己的前置条件(`status_probe`) | Robot **suite**;pytest 一个目录 + conftest.py |
| case | 一条用例 | test case |
**「suite」只作总称**(泛指整个测试集),不作 YAML 键。集合级的角色由 **category** 承担:`cases/<cat>.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/<cat>.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 的设备、服务与 cleanup/rollback 契约保证,原始 risk 仍必须报告;客户端能力由 journey 用例验证。
### 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` 一种前置,出现第二种再抽象 |