关于 backend-test-creator
backend-test-creator 是用于 Datex Studio 的技能,可在分支上创建或修改后端测试套件。它围绕 Mocha 生命周期钩子和测试用例组织流程,并通过 CLI 验证后提交配置。
主要功能
- 01创建或修改后端测试套件
- 02配置四种 Mocha 生命周期钩子
- 03通过测试用例流程组织断言
- 04管理套件级共享状态
- 05验证并提交配置 JSON
适用场景
- 01为流程或操作编写确定性测试
- 02将测试逻辑从操作迁移到测试套件
- 03在分支上更新现有后端测试
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ datex/ skills --skill backend-test-creator安装后在 Claude Code 中输入/backend-test-creator确认已加载。
附带文件evals/evals.jsonreferences/backend-tests.md
详细介绍
后端测试创建器
在分支上创建或修改 Datex Studio 后端测试(configurationTypeId=24)——这是一个 mocha 测试套件,包含四个生命周期钩子(before_suite / after_suite / before_each / after_each)、一个 testCaseFlows[] 数组(每个条目都包装为一个 mocha it()),以及 $test 运行时全局变量上的套件级状态。后端测试在函数层级运行;通过 FootprintApi.extendedActions 桥接来调用操作。
参考资料
- ../datex-studio-shared/branch-setup.md — 分支/连接选择(各技能共用)
- references/backend-tests.md — 权威的后端测试参考:文件结构、钩子语义、运行时全局变量、模拟策略层级、Platform 待办事项
- ../datex-studio-conventions/file-format.md —
configurationTypeId表、-backendTest.json后缀、单行压缩 JSON 约定、\r\n编辑规则 - ../datex-studio-conventions/naming-conventions.md —
_test后缀、文件名主体匹配、referenceName==title规则 - ../datex-studio-runtime/runtime-globals.md — 钩子和测试用例代码字符串中可用的函数层级全局变量(
$test、$flows、$apis、$datasources等)——但不包括$db(见下方的层级合规规则) - ../datex-studio-runtime/calling-conventions.md — 函数层级调用矩阵;用于调用操作的
FootprintApi.extendedActions桥接 - ../action-creator/references/actions.md — 操作组件;后端测试取代了将操作当作测试的历史反模式
- ../type-definition-creator/references/type-definitions.md — 与
vars[]条目共用的属性描述符基准 - ../db-query/references/db.md —
$db谓词和补丁,供套件调用的函数层级测试支持垫片用于创建原始存储测试数据($db本身无法在测试用例代码中解析)
依赖项
requirements-gathering技能——如果对话上下文中尚无需求简报,则调用它来生成需求简报action-creator/function-creator技能——当被测流程或仅用于测试的包装流程尚未出现在分支上时调用type-definition-creator技能——当vars[]条目引用的接口或联合类型尚未出现在分支上时调用impact-analysis技能——将作为测试的操作重构出去时调用(只有在确认没有其他内容调用该操作后才删除它),或修改仅用于测试的包装流程签名时调用
CLI 生命周期
后端测试的创建通过 dxs configuration 进行——这是适用于所有平台配置类型的通用 CRUD 基元。没有 dxs backendtest 子命令,也不支持字段级修补;你需要构建(或获取并提取)完整的 JSON 请求体,编辑后再将整个请求体推回去。CLI 中的类型标识符是 backendtest(小写,与 ConfigurationEndpoints.normalize_type 的输出一致),而不是 backendTest。
创建新的后端测试:
# 1. Build body.json from scratch (see references/backend-tests.md → Minimal Valid Skeleton)
# 2. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate backendtest -b <branchId> -D body.json
# 3. Create
dxs configuration upsert backendtest -b <branchId> -D body.json
编辑现有后端测试:
# 1. Fetch — note the envelope wrapper
dxs configuration get backendtest <configId> -b <branchId> -O envelope.json
# 2. EXTRACT THE INNER BODY (round-trip footgun guard — see "Round-trip rule" below)
jq .json envelope.json > body.json
# 3. Edit body.json
# 4. Validate — gates the push. Exit 1 = errors found (read validation_errors, fix, re-run), not a broken CLI
dxs configuration validate backendtest -b <branchId> -D body.json
# 5. Push
dxs configuration upsert backendtest -b <branchId> -D body.json
往返规则(至关重要)
编辑现有配置时,绝不要将 envelope.json 直接传入 dxs configuration upsert——这样会悄无声息地破坏配置内容。上文修正后的流程(先用 jq 提取内部 .json,再进行编辑)是所有往返操作的必经步骤。有关规范的往返流程和底层错误,请参阅 ../datex-studio-shared/configuration-roundtrip.md。
后端测试 JSON 必须是单行压缩格式(与 -customType.json 使用相同约定)——推送时不要美化格式。如果为方便编辑而在本地美化了格式,请在运行 dxs configuration upsert 前重新压缩。在 code 字符串中,换行符为 \r\n(在 JSON 层中转义为 \\r\\n);编辑嵌入的代码时,优先使用 Python 的 json.load / json.dump,而不要直接替换原始字符串。
工作流
[Phase 1: Setup + Requirements]
Follow branch-setup.md for branch/connection selection
|
[requirements brief in context?]
+-----+-----+
| |
YES NO -> invoke `requirements-gathering`
| |
+-----+------+
|
[Phase 2: Decide test scope]
Consult references/backend-tests.md → "Purpose & When to Use"
and "Test Case Layout":
- test suite with deterministic cases -> backend-test
- pure transform, no pass/fail -> helper function, stop here
- live smoke check -> throwaway $flows.* call, stop here
Pick layout: one-flow-per-test (≤ ~25 cases) vs
one-flow-per-category (≥ ~30 cases); ambiguous 25-30 -> ask user
Action-as-test refactor? -> plan to delete the source action
after the suite lands; invoke `impact-analysis` before delete
|
[Phase 3: Author test body]
Build body.json:
- File shape (configurationTypeId=24, suffix -backendTest.json,
single-line minified, referenceName==title, _test stem)
- Four hook flows: before_suite / after_suite / before_each /
after_each (referenceNames EXACT — renaming silently unbinds)
- testCaseFlows[]: one flow per test (or per category)
- $test.vars seeded in before_suite; never $flow.vars
- Actions reached via $apis.<Package>.FootprintApi
.extendedActions.<action_name>(...) — NOT $flows.*
- Assertions hand-rolled (no chai/sinon/assert at runtime);
throw Error(message) with input context in the message
- Mocking strategy: prefer test-only wrapper flow; tolerate
mock_data param only on code already shaped that way; flag
runtime-mock seam as Platform TODO in before_suite comments
- Invoke `action-creator` / `function-creator` if the
flow-under-test or wrapper doesn't exist yet
|
[Phase 4: Validate + push]
dxs configuration validate backendtest -b <branchId> -D body.json
|
+----+----+
| |
CREATE MODIFY-EXISTING
| |
| use the corrected round-trip
| (get -O envelope -> jq .json -> body)
| |
+----+----+
|
v
dxs configuration upsert backendtest -b <branchId> -D body.json
(upsert creates or updates by referenceName — one command for both)
|
[Phase 5: Run the suite (optional)]
Trigger the mocha runner against the suite on the branch.
Watch for: hook never fires (referenceName drift), test code in
flows[] instead of testCaseFlows[] (silently ignored),
vacuously-passing "no X" assertions against fields the code no
longer produces.
|
[invoke `post-edit-verification`; then `component-validator`]
阶段详情
阶段 1:准备 + 需求
- 按照 ../datex-studio-shared/branch-setup.md 选择分支和连接。绝不要假设分支 ID——请让用户确认。
- 检查对话上下文中是否已有需求简述(由
requirements-gathering或其他调用技能生成)。- 已有简述——使用该简述。对于后端测试,简述应指出要测试的流程或操作、用于确保覆盖率的确定性用例、所需的夹具/模拟,以及这是全新测试套件还是对现有 action-as-test 的重构。
- 没有简述——先调用
requirements-gathering技能。“这真的是测试套件,还是只是冒烟检查?”这个问题对阶段 2 至关重要。
阶段 2:确定测试范围
编写测试前,请参阅 references/backend-tests.md → Purpose & When to Use。
如果工作内容是一个测试套件——通过多个用例、基线不变量、设置/清理和通过/失败报告来测试某个流程或操作——请选择后端测试。
- 绝不要编写新的 action-as-test。 此组件出现之前,工程师会把测试套件塞进某个操作的
code字符串中,并用手写的运行器执行。现在不再采用这种模式。遇到这类操作时(文件名通常以test_开头或以_test_action结尾),应计划将其重构为后端测试——在此编写新套件,然后仅在调用impact-analysis技能确认没有其他内容调用该操作后,才删除源操作。 - 如果逻辑是纯转换,不涉及通过/失败的概念,优先使用辅助函数,而不是后端测试——函数不会让你承担一个用不上的 mocha 运行器。
- 不要将后端测试用作实时数据的冒烟检查工具。 后端测试用于确定性测试套件。如果你需要确认“这个流程在生产环境中是否会返回合理的结果?”,应从临时操作手动调用
$flows.*,而不是编写后端测试。
从 references/backend-tests.md → Minimal Valid Skeleton 中选择测试用例布局:
- 每个逻辑测试使用一个流程——这是最符合 mocha 惯例的形式。适用于套件不超过约 25 个用例,或需要在 mocha 输出中筛选单个失败用例的情况。
- 每个类别使用一个流程,在流程内部遍历用例——
code字符串定义用例对象数组并循环处理。适用于每个测试一个流程会导致流程数超过约 30 个,或同一类别中的用例共享足够多的设置、值得放在一起的情况。合理的结构是约 15–20 个类别,每个类别约 2–5 个用例。
如果预计用例数落在含糊的 25–30 区间,或用户表示希望新套件与该功能中现有套件的粒度保持一致,请询问用户。
阶段 3:编写测试主体
根据 references/backend-tests.md → Minimal Valid Skeleton 中的骨架构建 body.json。要点如下:
-
文件基础信息。 遵循下方的 预检清单 和 ../datex-studio-conventions/universal-checklist.md;有关
-backendTest.json文件格式(以及_test文件名规则),请参阅 references/backend-tests.md → 文件位置与命名。 -
flows[]中必须恰好有四个钩子流程。 ReferenceName 为before_suite/after_suite/before_each/after_each——拼写必须完全一致,不得使用变体。根级别的beforeFlowConfig.flowId/afterFlowConfig.flowId/beforeEachFlowConfig.flowId/afterEachFlowConfig.flowId会通过referenceName(而非数字 id)解析到这四个流程之一。如果钩子流程的 referenceName 是beforeSuite或setup,运行器会静默地不绑定它——钩子不会触发,用户却以为它已经生效。referenceNameMocha 钩子 典型内容 before_suitebefore()在 $test.vars上初始化套件级状态:断言辅助函数、模拟对象构建器、开销较大的一次性固定数据。after_suiteafter()释放 before_suite持久化的所有内容。通常为空。before_eachbeforeEach()重置每个测试的状态(计数器、临时集合),避免用例之间相互影响。 after_eachafterEach()清理每个测试的数据。通常为空。 -
测试用例必须放在
testCaseFlows[]中。 每个条目都是一个流程,其唯一步骤的executeCodeConfig.code是一个it()的主体。该流程的referenceName会成为 Mocha 输出中的测试名称。flows[]中除四个钩子外的任何内容都会被运行器静默忽略——新手常因把测试用例放入错误的数组而导致静默失败。每个测试用例的code默认脚手架采用 Arrange / Act / Assert 模板:// Arrange // Act // Assert -
vars[]是套件级状态机制,可通过$test运行时全局变量($test.vars.<id>)从任意钩子或测试用例代码中访问。后端测试拥有独立于$flow的运行时全局变量——运行器会在每次钩子和测试用例执行时填充$test,而$test.vars会在整个套件生命周期内持久存在(before_suite → before_each + 用例 + after_each 循环 → after_suite)。每个 var 都是一个属性描述符,其格式与接口属性相同——请参阅 ../type-definition-creator/references/type-definitions.md。存储在 vars 上的函数必须标注为object类型(描述符架构中没有function基本类型)。 -
遵守
code字符串中的层级要求。 后端测试运行在 函数层级,因此适用函数层级调用矩阵:$test.vars.<id>— 在vars[]中声明的套件级状态。用于替代$flow.vars——在后端测试中写入$flow.vars无法解析。$apis.<Package>.FootprintApi.extendedActions.<action_name>({ ... })— 调用一个操作。**这是从后端测试中调用操作的唯一方式。**这里不支持操作 → 操作语法$flows.<Package>.<action_name>。$flows.<Package>.<function_name>({ ... })— 调用另一个函数。此层级的$flows只能访问函数。$datasources.<Package>.<name>.get({ ... })— 仅限函数层级的数据源(-datasource.json)。无法访问 FPDS(-footprintDatasource.json)。$db.<Package>.<storage>— **不可用。**Studio Validate 会拒绝后端测试钩子/测试用例代码中的$db,并报错Cannot find name '$db'(已于 2026-07-15 验证——一个使用$db的套件,其流程可追溯到六月,却在首次上传时失败),尽管其他函数层级服务都能解析。存储访问必须通过同一包中的$flows:如果测试本来就在验证契约,优先使用该功能真正的管理/动词流程;对于直接访问测试数据(插入旧版行、断言列状态、清理数据),请添加一个小型函数层级测试支持垫片,并在测试中调用它(已发布的先例:LaborManagement.task_control_engine_test完全不使用$db;SalesOrders.oa_test_support_flow是垫片模式)。../db-query/references/db.md 中的规则适用于垫片内部。
请参阅 ../datex-studio-runtime/runtime-globals.md 和 ../datex-studio-runtime/calling-conventions.md。
-
断言契约是出错即抛出。默认不会加载任何断言库——没有
expect、assert、chai 或 sinon。测试用例的代码正常返回即为通过,抛出异常即为失败。运行器会在 mocha 输出中显示Error.message,因此请认真编写错误消息——“预期 3 个分配项,实际得到 2 个(lotIds = [1, 2])”远胜于“断言失败”。在
before_suite中定义一次辅助函数,并将其存放在$test.vars上:// before_suite code $test.vars.assertEquals = (description, expected, actual) => { if (JSON.stringify(expected) !== JSON.stringify(actual)) { throw new Error(`${description}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`); } }; $test.vars.buildMockData = (overrides) => { /* ... */ };每个测试用例都可以复用这些辅助函数。调用被测操作时,需通过
FootprintApi.extendedActions桥接:// test case code const input = { context: 9, request: {...}, mock_data: $test.vars.buildMockData({...}) }; const output = await $apis.Allocations.FootprintApi.extendedActions.plan_inventory_consumption_action(input); $test.vars.assertEquals('result count', 2, output.result.length); $test.vars.assertEquals('first lot id', 1, output.result[0].lot_id); -
**测试用例中的错误传递。**当测试用例调用的流程本身抛出异常时,mocha 会显示该错误。这通常正是你想要的结果——除非要将某种特定失败模式转换成更清晰的断言错误,否则不要用 try/catch 包裹调用。如果确实捕获异常,务必添加上下文后重新抛出(
throw new Error(\calling plan_inventory_consumption_action with context=9 failed: ${e.message}`)`)——在测试用例中吞掉错误,在 mocha 输出中与“代码已正常运行”无法区分,这是最糟糕的假通过。钩子也适用相同规则,但有一点不同:钩子内部抛出异常会中止整个套件(mocha 的默认行为)。如果需要非致命失败,请在钩子内部吞掉异常,并将其记录到
$test.vars,以便之后由测试用例显示。 -
模拟策略。这里存在平台能力缺口:目前没有运行时切入点可拦截测试中的
$apis/$flows/$datasources/$db调用。这意味着后端测试无法像使用sinon或nock那样模拟 FootprintApi 响应。要向被测流程提供预置数据,唯一方法是将数据接入流程。以下分为三个层级,从优到劣:- 在运行时拦截 FootprintApi /
$flows— 目前不可用。 这是平台应努力实现的方向。在before_suite注释中标记为 Platform TODO,参见 references/backend-tests.md → Platform TODOs。 - 仅用于测试的包装工作流。 编写一个明确接收模拟数据结构的工作流,并使用生产工作流的真实契约调用它。测试调用包装工作流;生产代码调用真实工作流。这是新工作的正确选择。 调用
function-creator来编写包装工作流。 - 在生产工作流上添加
mock_data参数。 既有操作有时会带有此参数——这会永久污染生产契约。仅将其视为最后手段,且只适用于已经采用这种形式的代码。绝不要在新的生产工作流上添加mock_data参数。 如果测试新工作流的唯一方法是预先为模拟数据做准备,请提出异议,并改为按(2)构建仅用于测试的包装工作流。
- 在运行时拦截 FootprintApi /
-
将操作重构为测试。 如果你要将操作形式的测试(文件名通常以
test_开头或以_test_action结尾)转换为后端测试:a. 首先编写新的
*-backendTest.json套件;将操作code字符串中的测试用例复制到单独的testCaseFlows[]条目中(把手写的迭代改写为 mocha 用例;循环由运行器处理)。 b. 将套件级辅助函数从操作的 setup 块移到before_suite,并将它们存放在$test.vars上。 c. 将任何$flows.<Package>.<action>(...)调用改写为$apis.<Package>.FootprintApi.extendedActions.<action>(...)——层级从操作变为函数。 d. 推送新套件并确认它可以运行(阶段 5)。 e. 删除源操作前,调用impact-analysis技能,确认没有其他内容调用它。这个操作表面上可能只是个虚假的测试,但某处的真实调用方可能已经开始依赖它来执行生产工作——不要等删除后才发现。
阶段 4:验证并推送
# Validate the body locally against the branch. Exit 1 = validation found errors
# (read validation_errors, fix body.json, re-run) — not a broken CLI. Do not push on exit 1.
dxs configuration validate backendtest -b <branchId> -D body.json
# For a new backend test
dxs configuration upsert backendtest -b <branchId> -D body.json
# For modify-existing (round-trip — never skip the jq extract)
dxs configuration get backendtest <configId> -b <branchId> -O envelope.json
jq .json envelope.json > body.json
# ... edit body.json ...
dxs configuration upsert backendtest -b <branchId> -D body.json
验证会在推送前发现缺失的必填字段、vars[] 中格式错误的属性描述符结构,以及引用错误。它不会发现静默绑定失败(钩子工作流的 referenceName 错误)、将测试代码放在 flows[] 而不是 testCaseFlows[] 中,或断言恒通过等问题——这些属于运行时问题,应通过阶段 5 的验证以及下文的 references/backend-tests.md → Pre-Flight Checklist 进行审查。
阶段 5:运行套件(可选)
如果平台提供了在当前分支上触发 mocha 运行器来执行套件的方式(Studio 按钮、CLI 子命令,或对已知测试入口点的手动 $flows.* 调用),请运行并确认:
- 四个钩子确实触发(重命名后的钩子,例如
beforeSuite,会静默地无法绑定;测试用例会运行,但缺少 setup)。 - 每个测试用例都会在 mocha 输出中作为独立的
it()显示(或按类别显示,具体取决于阶段 2 选择的布局)。四个钩子之外的flows[]中的任何内容都会被静默忽略——如果输出中“缺少”某个测试用例,原因就在这里。 - 不存在断言恒通过的“没有 X”测试——如果测试针对已不再包含
foo的输出断言(result.foo ?? []).length === 0,那么每次回归都会通过。通过同时断言正向对照中存在该字段来审查。
如果无法访问运行器,请重新获取配置(使用修正后的 jq .json 提取模式),并与 body.json 比较差异,以确认推送已生效;四个钩子绑定和 testCaseFlows[] 布局应与编写内容一致。
提交前检查清单
推送前,请完整检查 references/backend-tests.md 中的清单。快速版:
- 文件基础信息。 后缀为
-backendTest.json,configurationTypeId: 24,referenceName以_test结尾——还要通过通用检查(../datex-studio-conventions/universal-checklist.md)。 flows[]中必须恰好有四个钩子流程,其 referenceNames 分别为before_suite/after_suite/before_each/after_each。每个根级的beforeFlowConfig.flowId/afterFlowConfig.flowId/beforeEachFlowConfig.flowId/afterEachFlowConfig.flowId都必须通过 referenceName 解析到其中一个流程。钩子名称错误会导致它静默地不触发。- 测试用例应放在
testCaseFlows[]中,而不是flows[]中。flows[]中除四个钩子以外的任何内容都会被运行器静默忽略。 vars[]描述符必须完整。 每个变量都包含id、type、isCollection、objectType、isSecured;必需变量要添加required: true;联合类型要包含oneOf;常量要包含isConstant: true+constantValue。code字符串的换行符必须为\r\n。 在解码后的字符串中如此(JSON 层会将其转义为\\r\\n)。请使用 Python 的json.load/json.dump进行编辑——直接替换原始文本会破坏转义。- 不要假定存在
expect/ chai / sinon。 失败时抛出异常。手写断言辅助函数放在$test.vars上,并在before_suite中定义。 - 不要在生产流程中新增
mock_data参数。 如果测试套件需要模拟数据,优先使用仅供测试的包装流程。只有原本已有mock_data的代码才可以保留该参数;请在测试套件的before_suite注释中标明,并附上指向 Platform 中关于运行时拦截的 TODO 的链接。 - 测试用例布局遵循第 2 阶段的决策规则: 少于约 25 个用例时,每个测试对应一个流程;多于约 30 个时,每个类别对应一个流程;介于两者之间时,情况不明确。
- 断言消息要包含输入信息。
assertEquals('allocation order', [1, 2], order)会报告上下文;assertEquals('x', expected, actual)则不会。 - 不要编写恒为真的“没有 X”断言。 如果输出中已不存在
foo,对(result.foo ?? []).length === 0的断言将永远得到0 === 0。依赖缺失检查前,请先验证该字段路径仍然有效(正向控制)。
常见错误
| 错误 | 修正方法 |
|---|---|
将 Hook 流命名为 beforeSuite / setup / before,而不是 before_suite | 静默解绑——运行器会通过 referenceName 解析 beforeFlowConfig.flowId;名称不匹配意味着 Hook 永远不会触发,但不会引发错误。请使用确切名称 before_suite / after_suite / before_each / after_each。 |
将测试用例放在 flows[] 而不是 testCaseFlows[] 中 | 运行器只会将 testCaseFlows[] 包装为 mocha it()。四个 Hook 之外的任何内容若放在 flows[] 中,都会被静默忽略。 |
在测试用例中通过 $flows.<Package>.<action_name>(...) 调用 Action | 后端测试在函数层级运行;这种语法仅适用于 Action→Action。请改用 $apis.<Package>.FootprintApi.extendedActions.<action_name>({ ... })。 |
在 Hook 或测试用例代码中使用 $flow.vars 而不是 $test.vars | 后端测试有专用的运行时全局变量。$flow 无法解析。请将每处引用替换为 $test.vars.<id>。 |
认为 expect、chai、sinon 或 Node 内置的 assert 可用 | 默认不会加载这些库。请在 before_suite 中手动编写辅助函数,在失败时执行 throw new Error(...);并将它们存放在 $test.vars 上。 |
为了让后端测试能够模拟新生产流,给它添加 mock_data 参数 | 这会永久污染生产流的契约。请改为构建仅用于测试的包装流(调用 function-creator)。只有原本就采用这种结构的代码才允许使用 mock_data 参数;新代码绝不添加这类参数。 |
| 在测试用例中吞掉错误,以便“让测试套件继续运行” | 在 mocha 输出中,这与通过无法区分——这是最糟糕的一类误报。如果捕获了错误,请附上上下文后重新抛出。 |
| 推送前美化 JSON 格式 | 按平台约定,后端测试文件必须是单行压缩格式(与 -customType.json 相同)。请在执行 dxs configuration upsert 前重新压缩。 |
将 dxs configuration get -O envelope.json 的输出直接传给 dxs configuration upsert -D envelope.json | 这会静默破坏配置内容。编辑前务必先执行 jq .json envelope.json > body.json。请参阅上文的“往返规则”。 |
| 未检查调用方就删除作为测试的 Action | 该 Action 可能不只用于自测。删除前请调用 impact-analysis。 |
针对已不再包含 foo 的输出断言 (result.foo ?? []).length === 0 | 这会让所有回归问题都能侥幸通过。请添加正向检查,在已知良好的用例中断言 foo 存在;或者直接断言预期值,而不是断言其不存在。 |
description 超过 100 个字符 | SQL 列长度有限,推送将无法通过验证。请缩短描述。 |
referenceName ≠ title,或文件名主体与 referenceName 不匹配 | 查找结果 / mocha 输出标签会不正确。按照约定,这三个名称应一致:文件名主体 == referenceName == title,且都以 _test 结尾。 |
编辑后,请调用 post-edit-verification,以发现描述、JSON 或架构违规。最终审查时,请调用 component-validator。