生活分享
Skill 觸發與結果測試
分開驗證格式、選用條件及實際結果,用應觸發與不應觸發的案例修正技能描述。
閱讀時間約 15 分鐘 · 操作 30 分鐘

返回 Codex 教學總目錄Codex 學習中心:完整教學目錄從安裝、第一個任務到 MD 規則與進階整合,規劃 60 篇 Codex 教學、十個單元。依程度、平台、需求或指令搜尋下一篇;尚未公開的教學會標示狀態,方便安排學習路線。閱讀全文
目標與準備
本段提到的教學與資源: 技能資源分拆Skill 的腳本、參考文件與材料把重複邏輯與大型資料拆到必要的支援檔案,讓技能按需要讀取並驗證相對路徑。閱讀全文
「測試全過」在這裡有兩種不同範圍:下面的八項自動測試只證明檢查程式對指定輸入的結果;技能行為驗收還需要觀察選擇、讀檔、工具紀錄及回覆。兩者分開記錄,才不會把 Node.js 成功當成 Codex 已成功觸發技能。測試也不要求特定模型名稱,請記錄你當時實際使用的入口與可用選項。
步驟 1:列出正確結果與失敗條件
先看這張表,再寫測試。正常案例保留兩筆同名 Read;空陣列是有效輸入,不應因為沒有任務就報錯。反過來,重複 ID、錯誤型別或壞 JSON 必須明確失敗,不能先刪掉有問題的列,再回覆看似正常的總數。錯誤輸入不應在標準輸出混入半份成功 JSON,否則後續腳本很容易誤讀。
| 案例 | 程式退出碼 | 可接受結果 |
|---|---|---|
| 三筆任務、兩筆同名 | 0 | total 3、active 1、completed 2 |
| 空陣列 | 0 | 三個數量均為 0 |
| 重複 ID | 1 | Duplicate id,不輸出成功 JSON |
| completed 為字串 | 1 | Invalid task,不自動轉型 |
| 非陣列、壞 JSON、缺檔或缺參數 | 1 | 明確錯誤、沒有成功統計 |
步驟 2:加入八項可重跑測試
在 codex-skill-lab 根目錄新增 skill.test.mjs,貼入下列全部程式。它使用 Node.js 內建測試工具,不必安裝額外套件。每個案例在系統暫存目錄建立自己的輸入,呼叫真實統計器,再比對退出碼、輸出與原檔位元組。測試結束只清除自己剛建立的暫存資料夾,不碰 data/tasks.json。
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 和測試套件失敗混在一起。
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 仍沿用原數量。它是刻意製造的局部故障,不是本教學的最終版本。
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 時不依提示自動選用,但明確提及技能仍可使用。它控制選用方式,不是沙盒,也不會授予工具權限。
policy:
allow_implicit_invocation: false
保存後用兩個新任務重測:「統計 data/tasks-next.json 的待辦數量」不手動選技能;另一個則明確選 todo-summary 並指定同一檔案。前者不應自動選到這份技能,後者應仍可選用;兩者分別記錄實際讀取與工具活動。前者即使以普通檔案工具自行算對,也不能寫成技能已觸發。若更新未反映,重新啟動 Codex 後再測,仍保留未確認欄位。
這個比較需要在支援的實際入口執行;YAML 可解析或八項 Node 測試通過,都不能代替選用政策驗證。結束時只移除自己本次新建的 openai.yaml,或從備份恢復原檔,再開新任務確認原設定。若只想保留明確選用,就保留 false,並在驗收紀錄寫下這個決定及檔案位置。
步驟 5:記錄、修正,再測一次
建立下面的驗收紀錄,欄位沒量到就寫未執行,不要填入預期數字。一次只修一個問題:找不到資源就修路徑;描述造成誤選就修 description;程式計數錯才改腳本。每次保存修正前的版本與失敗案例,修正後重跑受影響的自動測試和對應新任務。修改主檔不代表舊任務已重新讀入。
# 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 用法查證。本教材提供的參考測試不等於已替你在各入口執行模型。
這套方法也能用於你日後建立的其他技能:先固定輸入、寫清失敗條件,再保存可重複的檢查。若只是需要可複製的需求或文件骨架,接著看範本與速查表提示詞、規則與交接範本索引依工作選擇提示詞、規則與交接範本,替換必要欄位並知道每段應貼到哪裡。閱讀全文;若要把多個技能交給別人安裝,再閱讀 PluginsPlugins 與外部服務Plugin 可以把 Skills 與 MCP 工具包在一起,提供可安裝的工作能力。安裝套件、連接外部帳號與實際執行工具是三個不同步驟;找到插件不代表已能讀你的服務資料。閱讀全文。示意圖 1 固定案例、2 執行與觀察、3 修正後重驗。
返回 Codex 教學總目錄Codex 學習中心:完整教學目錄從安裝、第一個任務到 MD 規則與進階整合,規劃 60 篇 Codex 教學、十個單元。依程度、平台、需求或指令搜尋下一篇;尚未公開的教學會標示狀態,方便安排學習路線。閱讀全文
閱讀完整文字說明
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 測試和瀏覽器操作驗收,並留下可重新啟動與還原的交接紀錄。
生活分享
用量與效率:減少重工
記錄任務條件、模型選項、時間與成果,找出能減少無效重試和過多上下文的調整。
引用本文的文章
最新旅遊情報攻略

情報
2026 韓國楓葉預測:雪嶽山 10 月 20 日、首爾近郊 10 月底、內藏山與漢拏山 11 月上旬
韓國山林廳 2026 年 9 月 22 日公布的楓紅高峰預測:雪嶽山 10 月 20 日,春川、國立樹木園到首爾植物園落在 10 月 28 日到 11 月 2 日,內藏山 11 月 4 日、漢拏山 11 月 6 日,整體比最近 5 年晚約 0.8 天。整理各地楓樹與銀杏的預測日、首爾出發怎麼排,以及出發前去哪裡看即時楓況。2026 年 10 月查證。
- 季節活動
- 自然
- 觀景

攻略胡志明市
胡志明市到頭頓一日遊:白藤碼頭搭高速船、船票與班次,下船就是胡梅纜車與耶穌基督像
人在胡志明市挪一天去頭頓看海:市中心的白藤高速船碼頭搭船,航程 120 分鐘到頭頓的胡梅碼頭,平日成人 320,000 越南盾、週末 350,000,回程末班平日 15:00。下船就是胡梅纜車站,同一條路上有白宮,小山頂上是耶穌基督像。平日一天只有兩班船,整天要從末班船倒推著排。
- 交通
- 行程範例
- 海灘

攻略沖繩
沖繩不開車攻略:單軌只到浦添,美麗海水族館要坐兩個多小時的巴士,回那霸的最後一班直達車 17:22 就開走
不租車的沖繩怎麼移動:那霸市區靠沖繩都市單軌電車(ゆいレール),那霸機場站到終點てだこ浦西 19 站、17 公里、37 分鐘,一日券 1,000 日圓;美麗海水族館有那霸機場直達的高速巴士,單程 2,000 日圓起、官方時刻表上 2 小時上下,下車後還要走 10 分鐘;古宇利島要在今帰仁村役場轉車,當天來回光坐車就六個半小時;回程的最後一班直達車 17:22 就從記念公園前開走(2026 年 9 月查證)。
- 交通
- 行程範例
- 預算