
Testany Case Writing
- 26 installs
- 79 repo stars
- Updated May 6, 2026
- testany-io/testany-agent-skills
Helps with testing & qa tasks.
About
testany-case-writing is a Claude Code skill for testing & qa. It helps solo builders move faster with AI-assisted development.
- testany-case-writing
- Testing & QA
- AI-coding skill
Testany Case Writing by the numbers
- 26 all-time installs (skills.sh)
- Ranked #1,382 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/testany-io/testany-agent-skills --skill testany-case-writingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 26 |
|---|---|
| repo stars | ★ 79 |
| Last updated | May 6, 2026 |
| Repository | testany-io/testany-agent-skills ↗ |
What it does
Helps with testing & qa tasks.
Files
Testany Platform Case Writing
将传统测试场景拆解为 Testany platform cases,并生成可注册到 Testany 平台的脚本、metadata 和 ZIP 包。
用户输入: $ARGUMENTS
---
宿主能力适配
- 如果宿主支持 slash command,可把
testany-case作为推荐注册入口,把testany-pipeline作为推荐编排入口。 - 如果宿主不支持 slash command,则直接在当前线程继续对应 workflow。
---
上游输入优先级
按以下优先级选择输入模式:
1. Primary:approved Test Spec + `Testany Automation Handoff`
- 来自
testany-eng Testany Automation Handoff.status = ready | partial- 这是本 skill 的首选输入
2. Secondary:approved Test Spec,但没有 handoff
- 可以继续,但需要自己补做 scenario grouping / executor / split decisions
3. Fallback:用户自然语言 / 现有测试设计文档
- 仅在前两者都没有时使用
如果输入来自 Test Spec,优先参考:
../../../testany-eng/references/testany-automation-handoff-contract.mdTestany Automation Handoffsection 中的scenario_groupssource_case_ids、recommended_executor、platform_case_strategy
若输入 Test Spec 仍是 draft / in_review:
- 默认不要直接把它当正式自动化基线
- 应先建议用户完成
testany-eng的/test-reviewer - 只有当用户明确接受 exploratory / 草案态自动化拆分时,才继续,并显式标注为低置信度
---
先统一心智模型
在开始之前,先按 automation-model.md 理解对象边界:
- 用户给出的通常是 traditional test scenario,也就是完整测试场景/业务验证目标。
- 本 skill 产出的是 Testany platform case,也就是可复用的原子自动化步骤包。
- pipeline 才是 Testany 的执行与编排单元。
- trigger 是
Plan / Manual Trigger / Gatekeeper,作用于 pipeline,而不是 case。
重要结论:
- 不要默认把“一个传统测试场景”直接写成“一个 Testany case”。
- 先判断该场景需要拆成几个 platform cases,再写脚本和 ZIP。
---
职责
- 消费传统测试场景输入:approved
test-spec、Testany Automation Handoff、用户自然语言、现有测试设计文档 - 判断一个场景要拆成几个 Testany platform cases
- 为每个 platform case 选择合适的 Executor
- 生成每个 platform case 的 metadata、脚本和 ZIP 包
- 产出面向下游的 automation design / decomposition summary
---
不负责的事情
- 不负责把 case 注册到 Testany 平台;这属于
testany-case - 不负责创建/更新 pipeline;这属于
testany-pipeline - 不负责配置 Plan / Manual Trigger / Gatekeeper;这属于
testany-trigger
---
工作流程
Phase 0: 判定输入模式与可信度
如果输入来自 Test Spec,先检查:
1. Test Spec 是否为 approved 2. 是否存在 Testany Automation Handoff 3. Testany Automation Handoff.status 是 ready、partial 还是 not_planned
处理规则:
ready:把 handoff 当作第一优先输入partial:把 handoff 当作基线,并集中追问open_questionsnot_planned:默认不要继续;只有用户明确要求覆盖该决定时才继续- section 缺失:降级为 Secondary 模式,从 Test Spec 正文补做 decomposition
Phase 1: 理解输入场景
优先收集以下信息: 1. 场景目标:要验证什么业务行为或系统行为 2. 输入来源:来自 approved test-spec、Testany Automation Handoff、用户口述,还是已有测试文档 3. 技术约束:API / UI / Java / Python / Postman / Playwright 4. 关键依赖:登录态、创建资源、清理动作、失败分支、回滚动作
Phase 2: 先做 decomposition,再写代码
必须先判断一个传统测试场景在 Testany 平台上应拆成几个 platform cases。
若上游已经给出 Testany Automation Handoff:
- 优先沿用
scenario_groups - 优先沿用
recommended_executor - 优先沿用
platform_case_strategy - 只有当 handoff 与 Test Spec 正文明显冲突时,才回退到人工重新分解,并把冲突显式回报给用户
优先拆成多个 platform cases 的情况:
- 某一步会产出 relay 输出给下游复用
- 某一步本身是可复用前置条件,例如登录、创建资源、清理资源
- 不同步骤需要不同 executor / runtime
- 存在条件分支、失败分支或
expect: fail - 你希望主流程、校验流程、清理流程分开维护
可以保持为单个 platform case 的情况:
- 整个动作天然原子
- 不需要 relay 给下游
- 不需要条件分支或跨 case 依赖
- 单一 executor 即可稳定表达
Phase 3: 为每个 platform case 产出 package
每个 platform case 至少要产出:
namedescriptioncase_labelsexecutorpath或commandenvironment_variables- 如需 relay:
type=output的输出变量 - 代码文件
- ZIP 包
Phase 4: 明确 downstream handoff
完成 case package 后,必须显式说明:
- 本次共拆出多少个 platform cases
- 每个 case 的职责、输入、输出、executor
- 每个 case 对应的
source_case_ids - 哪些 case 之间存在依赖
- 是否需要 relay
- 是否需要
testany-pipeline继续编排
强制规则:
- 如果存在依赖、relay、条件分支、清理分支、失败分支,必须产出“需要后续 pipeline 编排”的结论,不能停在 ZIP。
- 即使只有一个 platform case,只要用户要的是“可执行资产”,也应明确说明后续仍需要一条 pipeline 才能在 Testany 中运行。
Phase 5: 引导下游 workflow
- 如果用户要把 package 注册到平台:切到
testany-case - 如果用户要形成可执行链路:继续到
testany-pipeline - 如果用户要配置执行入口:再继续到
testany-trigger
---
推荐输出结构
1. Scenario Summary
- 原始传统测试场景是什么
- 为什么这样拆分
2. Platform Case Inventory
对每个 platform case 给出:
- 别名 / 本地文件名
source_case_ids- 目标动作
- executor
- 输入变量
- 输出变量
- ZIP 包路径
3. Automation Design Summary
single-case runnable?:是否只是单个原子步骤pipeline required:默认yesdependencies:A -> B -> Crelay map:例如LOGIN.AUTH_TOKEN -> SUBSCRIBE.AUTH_TOKENbranching:是否存在whenFailed/expect: fail
4. Next Step
- 注册这些 platform cases →
testany-case - 组装 pipeline →
testany-pipeline
---
Executor 选择决策树
Platform case 类型
├─ API 调用 / Python 优先 → PyRes ✓
├─ API 调用 / 不想写代码 → Postman
├─ UI / E2E 步骤 → Playwright
└─ Java 项目测试 → Maven 或 Gradle根据选择的 Executor,参考对应模板:
| Executor | 模板文件 | 适用场景 |
|---|---|---|
| PyRes | pyres.md | Python API 测试(推荐) |
| Postman | postman.md | 快速 API 验证 |
| Playwright | playwright.md | UI/E2E 测试 |
| Maven/Gradle | maven.md | Java 项目测试 |
注意:executor是后端严格字符串。本 skill 涉及的取值为:pyres,postman,playwright,maven,gradle(平台还支持python,jmeter)。
---
环境变量类型(case_meta.environment_variables.type)
| 类型 | 用途 | 示例 |
|---|---|---|
env | 输入/普通配置(包括 relay 输入) | API_BASE_URL, AUTH_TOKEN |
output | Relay 输出 | ACCESS_TOKEN, USER_ID |
secrets | 引用 workspace Credential Safe 条目 | PASSWORD, API_KEY |
约束(与平台校验一致):
-type支持env/output/secrets
- name 必须以大写字母开头,只能包含大写字母、数字、下划线;同一 case 内必须唯一-env/output:name/value不能为空或仅空白字符;如需表达"空值",请显式填-
-secrets:必须填secret_ref: { workspace_key, credential_safe_key, credential_key },禁止填value;脚本里直接读同名环境变量即可(如os.getenv("PASSWORD"))
-secrets的credential_safe_key/credential_key如果未知,在注册阶段(testany-caseskill)用testany_list_credential_safes→testany_list_credential_keys查询(两个工具都需要runtime_uuid,返回签名 curl 由 agent 代为执行);详细流程见testany-case/references/executors.md
-secrets读回时附带只读字段status(valid/blocked/invalid)和status_reasons[];写入时不要传
---
Output Relay 关键规则
Relay 是 pipeline 层编排 + case 层输出配置 的组合能力。只有同时满足两端约束才有效。
Output Case
- 在 case metadata 中声明
type: output的变量 - 在代码中把同名 key POST 到
TESTANY_OUTPUT_RELAY_SERVICE
Input Case
- 在 case metadata 中声明
type: env的变量 - 在代码中作为环境变量读取
编排层
- 由
testany-pipeline在 pipeline YAML 中配置relay.key与relay.refKey - 只有 passed 的上游 case 才能提供 relay 数据
如果你已经识别出 relay 需求,必须在输出中明确告诉下游:
- 哪个 case 产出什么变量
- 哪个 case 消费该变量
- 后续需要
testany-pipeline进行 relay 编排
---
完成后
交付时必须告诉用户: 1. 本次传统测试场景被拆成了几个 platform cases 2. 已生成的文件列表和 ZIP 包位置 3. 哪些 case 需要注册到 Testany 4. 是否必须继续到 testany-pipeline 5. 如宿主支持 slash command,可建议 /testany-case 和 /testany-pipeline 6. 若本次输入来自 Test Spec,应明确回显所消费的 source_case_ids 与 scenario_groups
---
参考文档
本地 References
- Testany 自动化对象模型
- 测试设计原则
- Case 元数据规范
- PyRes (Python)
- Postman
- Playwright
- Maven/Gradle
文档(兜底)
如果本地 references 不足以解决问题,请查阅 Testany 文档中心;当本 skill 的示例与文档不一致时,以文档为准:
interface:
display_name: "Testany Case Writing"
short_description: "Draft cases and Testany-ready scripts"
icon_small: "./assets/testany-logo-small.png"
icon_large: "./assets/testany-logo.svg"
default_prompt: "Use $testany-case-writing to draft test cases and Testany-compatible scripts for this requirement."
Case 元数据规范
本规范定义了 Testany platform case 元数据的填写标准。遵循此规范可以让后续的 Pipeline 编排(无论是人还是 AI)更加高效准确。
---
为什么需要这个规范
Pipeline 编排需要完成三个任务:
| 任务 | 需要的信息 |
|---|---|
| 选择 platform cases | 这个 case step 负责什么功能/动作?关联哪个场景? |
| 确定顺序 | 这个 case step 依赖什么前置条件? |
| 配置 Relay | 这个 case step 需要什么输入?产生什么输出? |
如果 case 元数据不完整或不规范,编排者需要猜测或阅读脚本代码,增加工作量和出错概率。
:::note 这里的 case 指的是 Testany 平台上的 platform case,不是传统测试语义中的完整测试场景。 :::
---
字段映射规范
name(名称)
用途:简洁描述测试场景
格式:[动作] [对象] [可选:条件/结果]
示例:
✅ 订阅 Gallery Item 并验证资源创建
✅ 登录成功并获取 Token
✅ 编辑订阅资源被拒绝(只读约束)
❌ test_001
❌ 测试
❌ US-G006---
case_labels(标签)
用途:结构化分类,支持精确筛选
必须包含: 1. User Story 编号(如有关联需求) 2. 功能模块 3. 测试类型(可选)
格式:
["US-G006", "subscription", "gallery", "smoke"]| 位置 | 内容 | 示例 |
|---|---|---|
| 第 1 个 | User Story 编号 | US-G006, US-G007 |
| 第 2-N 个 | 功能模块 | subscription, gallery, login, payment |
| 最后 | 测试类型(可选) | smoke, regression, e2e |
示例:
✅ ["US-G006", "subscription", "gallery"]
✅ ["US-G001", "gallery", "browse", "smoke"]
✅ ["login", "auth", "regression"] // 无关联 US 时省略
❌ ["test"]
❌ []---
description(描述)
用途:自然语言描述,包含测试场景和前置条件
必须包含: 1. 测试场景:测试什么、验证什么 2. 前置条件:依赖什么状态或其他 case
格式模板:
[一句话描述测试场景]
验证点:
- [验证点 1]
- [验证点 2]
- ...
前置条件:[描述依赖的状态或 case]示例:
测试用户订阅 Gallery Item 后系统正确创建资源。
验证点:
- 资源创建成功(status 200)
- source = 'subscribed'
- source_gallery_item_id 指向正确的 Gallery Item
- subscribed_version 记录当前版本号
前置条件:需要登录状态(AUTH_TOKEN 来自 LOGIN case)简化版(适用于简单 case):
测试用户登录成功并获取认证令牌。
前置条件:无---
environment_variables(环境变量)
用途:定义输入/输出变量及其语义,以及 workspace Credential Safe 凭证绑定。
必须填写 `description` 字段,说明:
- 变量的含义
- 对于
type=env:数据来源(来自哪个 case) - 对于
type=output:数据用途(供哪些 case 使用) - 对于
type=secrets:凭证用途(用于访问什么资源)
格式:
type支持env、output、secretsname必须唯一type=env/type=output:必须填value,不能为空或仅空白字符;如需表达“空值”,请显式填-type=secrets:必须填secret_ref: { workspace_key, credential_safe_key, credential_key },禁止填value- 读回 case 时,每条
type=secrets行附带只读字段status(valid/blocked/invalid)和status_reasons[];写入时不要传
type=env / type=output:
{
"name": "VARIABLE_NAME",
"type": "env",
"value": "-",
"description": "变量语义说明"
}type=secrets:
{
"name": "PASSWORD",
"type": "secrets",
"secret_ref": {
"workspace_key": "WKS",
"credential_safe_key": "WKS-CS-0001",
"credential_key": "test-account-password"
},
"description": "测试账号密码;脚本里直接读同名环境变量 PASSWORD 即可"
}如果credential_safe_key/credential_key未知,在注册阶段用testany_list_credential_safes→testany_list_credential_keys查询;两个工具返回签名 URL/curl,由 agent 执行后从返回项里取key字段,不要用name。
示例:
输入变量(type=env):
{
"name": "AUTH_TOKEN",
"type": "env",
"value": "-",
"description": "登录认证令牌,来自 LOGIN case 的输出"
}输出变量(type=output):
{
"name": "RESOURCE_ID",
"type": "output",
"value": "-",
"description": "创建的订阅资源 ID,供 READONLY_CHECK 和 INSTRUCTION_CHECK cases 使用"
}secret 变量(type=secrets):
{
"name": "PASSWORD",
"type": "secrets",
"secret_ref": {
"workspace_key": "WKS",
"credential_safe_key": "WKS-CS-0001",
"credential_key": "test-account-password"
},
"description": "测试账号密码;脚本里直接读同名环境变量 PASSWORD 即可"
}错误示例:
❌ { "name": "TOKEN", "type": "env", "value": "-" } // 缺少 description
❌ { "name": "KEY", "type": "secrets", "value": "abc" } // secrets 禁止填 value
❌ { "name": "KEY", "type": "secrets" } // secrets 缺少 secret_ref
❌ { "name": "KEY", "type": "secrets", "secret_ref": { "workspace_key": "WKS" } } // secret_ref 三个字段都必填---
完整示例
Case: 订阅 Gallery Item
name: 订阅 Gallery Item 并验证资源创建
case_labels:
- US-G006
- subscription
- gallery
description: |
测试用户订阅 Gallery Item 后系统正确创建资源。
验证点:
- 资源创建成功
- source = 'subscribed'
- source_gallery_item_id 正确
- subscribed_version 记录正确
前置条件:需要登录状态(AUTH_TOKEN 来自 LOGIN case)
environment_variables:
- name: AUTH_TOKEN
type: env
value: "-"
description: 登录认证令牌,来自 LOGIN case 的输出
- name: GALLERY_ITEM_ID
type: env
value: "test-item-001"
description: 要订阅的 Gallery Item ID
- name: RESOURCE_ID
type: output
value: "-"
description: 创建的订阅资源 ID,供后续 READONLY_CHECK 和 INSTRUCTION_CHECK cases 使用Case: LOGIN(前置 case)
name: 登录成功并获取 Token
case_labels:
- login
- auth
- prerequisite
description: |
测试用户登录成功并获取认证令牌。
前置条件:无(这是其他 case 的前置)
environment_variables:
- name: USERNAME
type: env
value: "test@example.com"
description: 测试账号用户名
- name: PASSWORD
type: secrets
secret_ref:
workspace_key: WKS
credential_safe_key: WKS-CS-0001 # 若未知,注册阶段用 testany_list_credential_safes 查询后取其 key
credential_key: test-account-password # 若未知,注册阶段用 testany_list_credential_keys 查询后取其 key
description: 测试账号密码;脚本里直接读同名环境变量 PASSWORD 即可
- name: AUTH_TOKEN
type: output
value: "-"
description: 登录成功后的认证令牌,供所有需要登录状态的 case 使用---
检查清单
创建或更新 case 时,确认以下内容:
- [ ]
name是否清晰描述了测试场景? - [ ]
case_labels是否包含 User Story 编号(如有)和功能模块? - [ ]
description是否包含验证点和前置条件? - [ ] 每个
environment_variable是否都有description? - [ ]
type=env的变量是否说明了数据来源? - [ ]
type=output的变量是否说明了数据用途?
Maven / Gradle 模板
Maven 和 Gradle 执行器用于 Java 项目测试。
ZIP 结构 (Maven)
my-test.zip
├── pom.xml
└── src/test/java/com/example/
└── ApiTest.javaZIP 结构 (Gradle)
my-test.zip
├── build.gradle
└── src/test/java/com/example/
└── ApiTest.javaTrigger 配置
Maven:
{"executor": "maven", "trigger_path": "src/test/java/com/example/ApiTest.java"}Gradle:
{"executor": "gradle", "trigger_path": "src/test/java/com/example/ApiTest.java"}pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>api-tests</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>代码模板
package com.example;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import java.net.http.*;
import java.net.URI;
public class ApiTest {
private final String baseUrl = System.getenv("API_BASE_URL");
@Test
void testLogin() throws Exception {
String body = String.format(
"{\"username\":\"%s\",\"password\":\"%s\"}",
System.getenv("USERNAME"),
System.getenv("PASSWORD")
);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(baseUrl + "/api/login"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
assertEquals(200, response.statusCode());
}
}Relay 输出
import java.net.http.*;
import java.net.URI;
public void relayOutput(String key, String value) throws Exception {
String relayService = System.getenv("TESTANY_OUTPUT_RELAY_SERVICE");
if (relayService != null) {
String json = String.format("{\"%s\": \"%s\"}", key, value);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(relayService))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
}
}
// 使用
relayOutput("ACCESS_TOKEN", token);官方文档
Playwright 模板
Playwright 执行器用于 UI/E2E 测试。
ZIP 结构
my-test.zip
├── package.json
├── playwright.config.ts
└── tests/
└── example.spec.tsTrigger 配置
{
"executor": "playwright",
"trigger_path": "tests/example.spec.ts"
}package.json
{
"name": "playwright-tests",
"version": "1.0.0",
"devDependencies": {
"@playwright/test": "^1.40.0",
"axios": "^1.6.0"
}
}playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30000,
use: {
baseURL: process.env.APP_URL,
headless: true,
},
});代码模板
import { test, expect } from '@playwright/test';
test.describe('Login Flow', () => {
test('should login successfully', async ({ page }) => {
const username = process.env.USERNAME;
const password = process.env.PASSWORD;
await page.goto('/login');
await page.fill('#username', username!);
await page.fill('#password', password!);
await page.click('#submit');
await expect(page).toHaveURL('/dashboard');
});
});Relay 输出
import axios from 'axios';
const relayService = process.env.TESTANY_OUTPUT_RELAY_SERVICE;
if (relayService) {
await axios.post(relayService, {ACCESS_TOKEN: token});
}官方文档
Postman 模板
Postman 执行器适合不想写代码的用户,直接使用 Postman Collection。
ZIP 结构
my-test.zip
└── api-tests.postman_collection.jsonTrigger 配置
{
"executor": "postman",
"trigger_path": "api-tests.postman_collection.json"
}Collection 结构
{
"info": {
"name": "API Tests",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Login",
"request": {
"method": "POST",
"url": "{{API_BASE_URL}}/api/login",
"header": [{"key": "Content-Type", "value": "application/json"}],
"body": {
"mode": "raw",
"raw": "{\"username\": \"{{USERNAME}}\", \"password\": \"{{PASSWORD}}\"}"
}
},
"event": [{
"listen": "test",
"script": {
"exec": [
"pm.test('Status 200', () => pm.response.to.have.status(200));",
"pm.test('Has token', () => pm.expect(pm.response.json().token).to.exist);"
]
}
}]
}
]
}Relay 输出 (Tests 脚本)
在 Postman Tests 脚本中发送 relay 数据:
const data = pm.response.json();
pm.sendRequest({
url: pm.environment.get('TESTANY_OUTPUT_RELAY_SERVICE'),
method: 'POST',
header: {'Content-Type': 'application/json'},
body: {mode: 'raw', raw: JSON.stringify({ACCESS_TOKEN: data.token})}
});官方文档
PyRes (Python) 模板 - 推荐
PyRes 是 Testany 推荐的 Python 测试执行器,基于 pytest。
ZIP 结构
my-test.zip
├── tests/
│ └── test_api.py
└── requirements.txt (可选)Trigger 配置
{
"executor": "pyres",
"trigger_command": ["python", "-m", "pytest", "tests/", "-v"]
}代码模板
import os
import pytest
import requests
# 环境变量
API_BASE_URL = os.getenv("API_BASE_URL")
USERNAME = os.getenv("USERNAME")
PASSWORD = os.getenv("PASSWORD")
def test_login_success():
"""测试用户登录成功"""
response = requests.post(
f"{API_BASE_URL}/api/login",
json={"username": USERNAME, "password": PASSWORD}
)
assert response.status_code == 200
data = response.json()
assert "token" in data
def test_login_invalid_credentials():
"""测试无效凭据登录失败"""
response = requests.post(
f"{API_BASE_URL}/api/login",
json={"username": "invalid", "password": "wrong"}
)
assert response.status_code == 401Relay 输出
def relay_output(data: dict):
"""将数据 relay 给 pipeline 中的下游 case"""
relay_service = os.getenv("TESTANY_OUTPUT_RELAY_SERVICE")
if relay_service:
requests.post(relay_service, json=data)
# 使用
relay_output({"ACCESS_TOKEN": token, "USER_ID": user_id})凭证获取
在 case metadata 里把变量声明为 type: secrets,并填好 secret_ref(workspace_key / credential_safe_key / credential_key);脚本里直接读同名环境变量即可:
import os
# 假设 metadata 中声明过:
# - name: PASSWORD
# type: secrets
# secret_ref:
# workspace_key: WKS
# credential_safe_key: WKS-CS-0001
# credential_key: test-account-password
password = os.getenv("PASSWORD")不需要引入凭证获取 helper,也不需要额外 HTTP 调用。
>
如果credential_safe_key/credential_key未知,可在 case 注册阶段用 MCP 工具testany_list_credential_safes→testany_list_credential_keys查询;详细流程见testany-case/references/executors.md。
官方文档
测试设计原则
先区分 3 个层级
| 层级 | 含义 | 例子 |
|---|---|---|
Traditional Test Scenario | 传统测试语义中的完整测试场景 | “用户订阅成功并验证只读约束” |
Testany Platform Case | Testany 平台中的原子自动化步骤包 | LOGIN、SUBSCRIBE、READONLY_CHECK |
Assertion | 单个 platform case 内部的验证点 | status == 200、source == subscribed |
关键结论:
test-spec产出的是Traditional Test Scenario- 若上游来自
testany-eng,优先读取其中的Testany Automation Handoff,用它收敛 Scenario 分组、Executor 建议与 split hints testany-case-writing写的是Testany Platform Case- 一个传统测试场景通常会拆成多个 platform cases,再由 pipeline 编排
Testany Platform Case vs Assertion
| 概念 | 定义 | 粒度 |
|---|---|---|
| Platform Case | 在 Testany 平台中注册和复用的原子自动化步骤包 | 中粒度 |
| Assertion | Platform Case 内部的单个验证点 | 细粒度 |
关系:一个 Platform Case 包含多个 Assertions。
Platform Case: 订阅 Gallery Item
├── Assertion 1: 资源创建成功
├── Assertion 2: source = 'subscribed'
├── Assertion 3: source_gallery_item_id 正确
└── Assertion 4: subscribed_version 记录正确---
从 PRD 到 Traditional Test Scenario 的设计流程
错误做法:Checklist 思维
PRD 验收标准 (AC) Test Cases
───────────────── ──────────
AC1: 按钮存在 → Case 1: 验证按钮
AC2: 创建资源 → Case 2: 验证创建
AC3: source 正确 → Case 3: 验证 source
AC4: item_id 正确 → Case 4: 验证 item_id
... ...
❌ 每个 AC = 一个 Test Case = 过度拆分正确做法:场景思维
PRD 验收标准 (AC) Test Cases
───────────────── ──────────
AC1: 按钮存在 → Case 1: UI 验证(独立场景)
AC2: 创建资源 ─┐
AC3: source 正确 ├→ Case 2: 订阅流程(一个场景,多个 assertions)
AC4: item_id 正确 │
AC5: version 正确 ─┘
AC6: 只读约束 → Case 3: 权限约束(独立场景)
AC7: 信息不可见 → Case 4: 信息保护(独立场景)
✓ 先按用户行为/系统场景划分
---
## 从 Traditional Test Scenario 到 Testany Platform Cases
场景确定之后,还要继续判断它在 Testany 平台上应拆成几个 platform cases。
### 需要拆分的典型信号
- 某一步会产出 relay 输出给下游复用
- 某一步本身就是可复用前置动作,例如登录、创建资源、清理资源
- 不同步骤需要不同 executor / runtime
- 存在条件分支、失败分支或 `expect: fail`
- 你希望将主流程验证与后置验证拆开维护
### 不需要继续拆分的典型信号
- 整个动作天然原子
- 同一 executor 即可表达
- 没有 relay、条件分支或跨步骤依赖
- 多个 assertions 都属于同一原子动作的自然结果---
Traditional Test Scenario 划分原则
原则 1: 一个 Traditional Test Scenario = 一个完整用户行为/业务验证目标
问:用户做了什么?
- 用户点击订阅按钮 → 一个 Traditional Test Scenario
- 用户尝试编辑订阅资源 → 一个 Traditional Test Scenario
- 用户尝试查看 Instruction → 一个 Traditional Test Scenario
原则 2: 相关验证点合并为 Assertions,再决定是否需要多个 Platform Cases
问:这些验证点是同一个行为的结果吗?
- 订阅后资源创建 + source 正确 + version 正确 = 同一原子动作的多个检查点 → 可合并在同一个 Platform Case
- 订阅成功 vs 编辑被拒绝 = 不同动作,且后者依赖前者产物 → 更适合拆成多个 Platform Cases
原则 3: 独立场景独立 Scenario;可复用步骤独立 Platform Case
问:这个验证需要独立的前置条件或操作吗?
- UI 按钮存在(无需登录)→ 独立 Scenario
- 只读约束(需要先订阅)→ 独立 Scenario;在平台实现上通常依赖前置 Platform Case
---
常见错误模式
错误 1: 过度拆分
❌ 错误:
- Case 1: 验证 status code = 200
- Case 2: 验证 response 有 token
- Case 3: 验证 token 格式正确
✓ 正确:
- Case 1: 登录成功
- assert status == 200
- assert 'token' in response
- assert token matches pattern错误 2: 过度合并
❌ 错误:
- Case 1: 测试所有登录场景(成功 + 失败 + 锁定 + ...)
✓ 正确:
- Case 1: 登录成功
- Case 2: 登录失败(无效凭据)
- Case 3: 账户锁定错误 3: 混淆 AC 和 Test Case
❌ 错误:把 PRD 的每个 AC 编号直接映射为 Test Case
✓ 正确:分析 AC 背后的用户行为,按行为划分 Test Case---
实战示例
需求:US-G006 订阅 Item
验收标准: 1. 详情页提供"订阅"按钮 2. 点击后创建新资源 3. source = 'subscribed' 4. source_gallery_item_id 正确 5. subscribed_version 记录 6. 只读约束 7. Instruction 不可见
Traditional Test Scenario 设计:
| Scenario | 场景 | 在平台上的建议实现 |
|---|---|---|
BTN_VISIBLE | 订阅按钮存在 | 1 个独立 platform case |
SUBSCRIBE_FLOW | 订阅并验证资源创建 | 登录 case + 订阅 case |
READONLY | 只读约束 | 依赖订阅结果的独立 platform case |
HIDE_INST | Instruction 不可见 | 依赖订阅结果的独立 platform case |
Pipeline YAML:
注意:上表中的别名仅用于讲解;实际 Pipeline YAML 的run/whenPassed/whenFailed必须填写 Testany Test Case Key(8 位大写十六进制,如AC2F5A50)。
kind: rule/v1.3
spec:
rules:
- run: A1B2C3D4 # AC1_BTN: 独立 UI 测试
- run: B2C3D4E5 # LOGIN: 前置
- run: C3D4E5F6 # SUBSCRIBE: 主流程 + 4 个 assertions
whenPassed: B2C3D4E5
relay:
- key: AUTH_TOKEN
refKey: B2C3D4E5/AUTH_TOKEN
- run: D4E5F6A7 # READONLY: 独立行为测试
whenPassed: C3D4E5F6
relay:
- key: AUTH_TOKEN
refKey: B2C3D4E5/AUTH_TOKEN
- key: RESOURCE_ID
refKey: C3D4E5F6/RESOURCE_ID
- run: E5F6A7B8 # HIDE_INST: 独立行为测试
whenPassed: C3D4E5F6
relay:
- key: AUTH_TOKEN
refKey: B2C3D4E5/AUTH_TOKEN
- key: RESOURCE_ID
refKey: C3D4E5F6/RESOURCE_ID结果:
- 7 个 AC 先收敛成 4 个 Traditional Test Scenarios
- 再映射成 5 个 Testany Platform Cases
- 最终通过一条 pipeline 编排运行