生活分享

Skill 触发与结果测试

分开验证格式、选用条件及实际结果,用应触发与不应触发的案例修正技能描述。

阅读时间约 15 分钟 · 操作 30 分钟

原创流程示意图,非产品界面截图。
图片:Mokaair (© Mokaair)
本篇目录
  1. 目标与准备
  2. 步骤 1:列出正确结果与失败条件
  3. 步骤 2:加入八项可重跑测试
  4. 步骤 3:确认测试真的能抓到错误
  5. 步骤 4:设计技能行为验收
  6. 步骤 5:记录、修正,再测一次

目标与准备

本段提到的教学与资源:

「测试全过」在这里有两种不同范围:下面的八项自动测试只证明检查程式对指定输入的结果;技能行为验收还需要观察选择、读档、工具纪录及回复。两者分开记录,才不会把 Node.js 成功当成 Codex 已成功触发技能。测试也不要求特定模型名称,请记录你当时实际使用的入口与可用选项。

步骤 1:列出正确结果与失败条件

先看这张表,再写测试。正常案例保留两笔同名 Read;空阵列是有效输入,不应因为没有任务就报错。反过来,重复 ID、错误型别或坏 JSON 必须明确失败,不能先删掉有问题的列,再回复看似正常的总数。错误输入不应在标准输出混入半份成功 JSON,否则后续脚本很容易误读。

案例程式退出码可接受结果
三笔任务、两笔同名0total 3、active 1、completed 2
空阵列0三个数量均为 0
重复 ID1Duplicate id,不输出成功 JSON
completed 为字串1Invalid task,不自动转型
非阵列、坏 JSON、缺档或缺参数1明确错误、没有成功统计

步骤 2:加入八项可重跑测试

在 codex-skill-lab 根目录新增 skill.test.mjs,贴入下列全部程式。它使用 Node.js 内建测试工具,不必安装额外套件。每个案例在系统暂存目录建立自己的输入,呼叫真实统计器,再比对退出码、输出与原档位元组。测试结束只清除自己刚建立的暂存资料夹,不碰 data/tasks.json。

skill.test.mjs · javascript
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { resolve, join, dirname } from 'node:path';
import { spawnSync } from 'node:child_process';

const script = resolve('.agents/skills/todo-summary/scripts/count-tasks.mjs');
const tasks = [
  { id: 'a', title: 'Read', completed: true },
  { id: 'b', title: 'Build', completed: false },
  { id: 'c', title: 'Read', completed: true },
];
function run(input, verify, missing = false) {
  const folder = mkdtempSync(join(tmpdir(), 'todo-skill-test-'));
  const file = join(folder, 'input.json');
  try {
    if (!missing) writeFileSync(file, input, 'utf8');
    const before = missing ? null : readFileSync(file);
    const result = spawnSync(process.execPath, [script, file], { encoding: 'utf8' });
    assert.equal(result.error, undefined);
    verify(result);
    if (!missing) assert.deepEqual(readFileSync(file), before, 'Input changed');
  } finally {
    assert.equal(dirname(resolve(folder)), resolve(tmpdir()));
    rmSync(folder, { recursive: true });
  }
}
function fails(input, pattern) {
  run(input, result => {
    assert.equal(result.status, 1);
    assert.equal(result.stdout, '');
    assert.match(result.stderr, pattern);
  });
}
test('keeps repeated titles as three records', () => run(JSON.stringify(tasks), result => {
  assert.equal(result.status, 0);
  assert.equal(result.stderr, '');
  assert.deepEqual(JSON.parse(result.stdout), { total: 3, active: 1, completed: 2 });
}));
test('accepts an empty array', () => run('[]', result => {
  assert.equal(result.status, 0);
  assert.deepEqual(JSON.parse(result.stdout), { total: 0, active: 0, completed: 0 });
}));
test('rejects duplicate IDs', () => fails(JSON.stringify([tasks[0], tasks[0]]), /Duplicate id/));
test('rejects string completed', () => fails(JSON.stringify([{ ...tasks[0], completed: 'true' }]), /Invalid task/));
test('rejects a non-array', () => fails('{}', /Input must be an array/));
test('rejects malformed JSON', () => fails('{', /\S/));
test('reports a missing file', () => run('', result => {
  assert.equal(result.status, 1);
  assert.equal(result.stdout, '');
  assert.match(result.stderr, /ENOENT/);
}, true));
test('requires an input argument', () => {
  const result = spawnSync(process.execPath, [script], { encoding: 'utf8' });
  assert.equal(result.status, 1);
  assert.equal(result.stdout, '');
  assert.match(result.stderr, /Usage:/);
});

保持终端机位于练习根目录,Windows PowerShell、macOS、Linux 都执行下面命令。正常的检查器应得到 8 项通过、0 项失败,测试程式本身退出 0。表格中预期失败的输入,会因为被正确拒绝而让对应测试通过;不要把统计器退出 1 和测试套件失败混在一起。

在练习根目录执行 · sh
node --test skill.test.mjs

如果第一项说找不到脚本,先检查根目录和 .agents/skills/todo-summary/scripts/count-tasks.mjs 的实际位置。若全部案例都变成 Usage,看看测试档是否仍传入 file 参数;若只有 JSON 错误文字不同,测试只要求有错误讯息,不依赖某一版 Node.js 的完整标点。先保留错误输出,再比对版本,别为了绿灯而删掉负面案例。

步骤 3:确认测试真的能抓到错误

先把 count-tasks.mjs 复制成 count-tasks.saved.mjs,确认备份存在。只在原档的 result 那一行,把 total: tasks.length 暂时改成下面片段,其余保留。这模拟「依标题计数」的错误:两笔 Read 被算成一笔,但 active 与 completed 仍沿用原数量。它是刻意制造的局部故障,不是本教学的最终版本。

只替换 result 内的 total 栏位 · javascript
total: new Set(tasks.map(task => task.title)).size

重跑相同命令,应出现 7 项通过、1 项失败,失败名称是 keeps repeated titles as three records。观察 actual total 为 2、expected 为 3,这才是测试抓到原本契约不允许的行为。接著只把原档从刚才备份还原,再跑一次应恢复 8 项通过。保留前、故障、还原三次摘要,不把最后一张绿灯当成全部过程。

步骤 4:设计技能行为验收

程式还原之后,开始检查 Codex。每一列使用新的任务,先确认工作资料夹,再依表格送出需求;不得让前一次已读规格、已完成报告或手动补充的答案污染下一列。每次记录输入原文、是否手动选技能、实际读取档案、工具命令、退出码与结果,才能判断问题发生在选择还是执行。

情境送出的自然语言需求检查重点
明确使用选取 todo-summary,统计 data/tasks.json,只在回复给报告读取资源、实际执行、3/1/2、输入不变
符合情境统计这份本机待办 JSON 的总数、未完成及完成数记录是否选到技能;没选到不冒称已使用
不符合情境将网站背景改成蓝色,先提出计划,不修改档案不应为这个需求执行待办统计器
必要资源缺少规格档改名后明确使用技能产生已验证报告说明缺档并停止,不创造规格或结果

桌面版透过 Skills 入口或 @ 选择;CLI/IDE 透过 /skills 或 $。自动选择依描述与上下文判断,没有选到不一定表示安装失败;先确认明确选取案例是否成功,再看 description 是否包含真正的用途与排除条件。不要把描述扩大成「所有任务都使用」,那会让不相关工作也载入这个技能。

只允许明确选用的对照练习

在这份独立的 todo-summary 技能中建立 agents/openai.yaml,使用下面设定。若已有此档,先保存副本并只合并 policy 栏位,保留原 interface 与 dependencies。依官方技能文件,allow_implicit_invocation 为 false 时不依提示自动选用,但明确提及技能仍可使用。它控制选用方式,不是沙盒,也不会授予工具权限。

agents/openai.yaml:合并片段 · yaml
policy:
  allow_implicit_invocation: false

保存后用两个新任务重测:「统计 data/tasks-next.json 的待办数量」不手动选技能;另一个则明确选 todo-summary 并指定同一档案。前者不应自动选到这份技能,后者应仍可选用;两者分别记录实际读取与工具活动。前者即使以普通档案工具自行算对,也不能写成技能已触发。若更新未反映,重新启动 Codex 后再测,仍保留未确认栏位。

这个比较需要在支援的实际入口执行;YAML 可解析或八项 Node 测试通过,都不能代替选用政策验证。结束时只移除自己本次新建的 openai.yaml,或从备份恢复原档,再开新任务确认原设定。若只想保留明确选用,就保留 false,并在验收纪录写下这个决定及档案位置。

步骤 5:记录、修正,再测一次

建立下面的验收纪录,栏位没量到就写未执行,不要填入预期数字。一次只修一个问题:找不到资源就修路径;描述造成误选就修 description;程式计数错才改脚本。每次保存修正前的版本与失败案例,修正后重跑受影响的自动测试和对应新任务。修改主档不代表旧任务已重新读入。

验收纪录范本 · markdown
# Skill acceptance record

Date and surface: <observed>
Node / Codex versions: <observed>
Model and effort, if shown: <observed or unavailable>
Skill path and revision: <exact local path and saved version>
Program tests: <command, exit, passes, failures>
Behavior case: <explicit / matching / outside-scope / missing-resource>
Request: <exact text>
Selected skill: <observed / not selected / unconfirmed>
Resources read and command executed: <evidence or not run>
Actual result and preserved input: <evidence>
Failure and one change: <description>
Fresh-task retest: <result or not run>
Remaining checks: <not run>

若代理宣称已执行但只提供预期结果,要求指出实际命令与工具输出;仍无法确认,就保留「未确认」,不要替它补证据。若多个同名技能同时出现,记录选到的路径,先停用或移出你这次新建的重复副本再重测,保留其他人的技能。不要任意改全域设定来掩盖本机教材路径错误。

练习结束前确认 input-format.md 已恢复原名、count-tasks.mjs 已恢复正确版、data/tasks.json 未被修改,最后八项测试全过。报告必须分开写程式测试与技能行为;未在 macOS 或 Linux 实际操作,就标记依文件与跨平台 Node.js 用法查证。本教材提供的参考测试不等于已替你在各入口执行模型。

这套方法也能用于你日后建立的其他技能:先固定输入、写清失败条件,再保存可重复的检查。若只是需要可复制的需求或文件骨架,接著看;若要把多个技能交给别人安装,再阅读 。示意图 1 固定案例、2 执行与观察、3 修正后重验。

原创流程示意图,非产品界面截图。
原创流程示意图,非产品界面截图。 · 图片:Mokaair (© Mokaair)
阅读完整文字说明

Three numbered stages: identify the starting point, perform the exercise, and verify the result. Original illustration, not a product screenshot.

回总目录

  • 生活分享

    Codex 学习中心:完整教程目录

    从安装、第一个任务到 MD 规则与进阶集成,规划 60 篇 Codex 教程、十个单元。按程度、平台、需求或命令搜索下一篇;尚未公开的教程会标示状态,方便安排学习路线。

  • 生活分享

    Worktree 与多任务隔离

    Worktree 让同一个 Git 程式库有不同的工作目录,各自承接不同分支。它适合让两项工作分开改档,但资料库、连接埠与外部服务仍可能共用,不能把档案隔离当成所有资源隔离。

  • 生活分享

    实战:制作小网站

    从 brief.md 规划并制作 Small Steps 待办网站,完成新增、完成、删除、筛选与本机资料保存。将 HTML、CSS、资料函式、画面事件与测试分开,以 Node 测试和浏览器操作验收,并留下可重新启动与还原的交接纪录。

  • 生活分享

    用量与效率:减少重工

    记录任务条件、模型选项、时间与成果,找出能减少无效重试和过多上下文的调整。

最新旅游情报攻略

资料来源

生活分享