測試 AI 應用:deterministic code、stochastic eval 與真實 UI 三層策略
分開測試 prompt 前後的 deterministic code、模型行為 eval、provider contract 與 Playwright 使用者旅程,避免只 snapshot 一段 AI 文字。
AI 應用同時包含 deterministic software 與 stochastic model behavior。若把兩者混在一個 E2E test,每次失敗都難以判斷是 parser、network、model、tool 或 UI;若只 snapshot 一段輸出,又會因合理措辭差異變得 flaky。正確策略是分層測試,讓每層回答單一問題。 截至 2026-07-29,OpenAI evaluation best practices 仍建議針對任務定義 eval、用代表性 dataset 與適當 grader,並持續評估變更。Playwright 官方提供會重試直到可觀察狀態的 web-first assertions;Vitest 則提供 controlled request mocking,但提醒每個 test 後清理 mock state。 實作步驟 第一層測 deterministic code:prompt builder、schema validation、redaction、cost calculation、retry decision、tool authorization、parser、state machine。這些應輸入固定、輸出精確,不呼叫 provider。 it("rejects a write tool without approval", () => { const decision = authorizeTool({ tool: "refund_order", approved: false, scopes: ["orders:read"], }) expect(decision).toEqual({ allowed: false, reason: "approval_required" }) }) 第二層測 provider adapter contract。mock HTTP/SDK response,覆蓋 normal、stream chunks、invalid JSON、empty output、tool call、429、timeout、5xx 與 aborted request。assert 送出的 model/config/prompt version、timeout 與 redacted log,不把真 API call 放每個 unit test。 第三層是離線 eval。從 production-like、已去識別資料建立 golden set,按 use case/risk/language/edge case切片。grader 優先 deterministic:JSON schema、citation existence、tool args、forbidden phrase;語意品質用 rubric + human labels,model grader需校準。記錄 dataset/prompt/model/config hash。 第四層是 tool simulation。建立 fake tools 回 success、denied、not found、conflict、slow、malicious content。驗證 model 不會在 denied 後換另一個越權 tool,不把 tool output 中的 injection 當系統指令,mutation 重試使用 idempotency。 第五層做少量 live integration,以專用 project/key、budget 與無敏感 fixtures確認真 provider schema/streaming。將 live test標記 separate schedule,不讓一般 PR 因 provider 波動全部紅;但 release前仍要有近期成功 evidence。 第六層用 Playwright 測 user journey:loading、streaming、cancel、retry、citation、copy、error、mobile、keyboard。使用 role/label/test id 與 auto-retrying assertion,等待可觀察結果,不寫固定 sleep。 await page.getByRole("button", { name: "Send" }).click() await expect(page.getByTestId("answer-status")).toHaveText("Complete") await expect(page.getByTestId("answer")).not.toBeEmpty() 第七層是 production canary/monitor。用無副作用 synthetic input 測 availability/schema/latency,追蹤 refusal、tool error、fallback、cost 與 feedback。真實 failure 需去識別後回到 regression dataset。 失敗與復原 eval 忽高忽低時,先固定 dataset、config、sampling 參數與 grader version,重跑多次看 variance。不要用單次 pass 作 release。若 model grader漂移,以 human-labeled calibration set 重估 threshold。 mock 全過、live integration fail 時,adapter mock 已落後真 API schema。保存 redacted response shape,更新 contract fixture,加 regression。不要把 provider error原文直接顯示使用者。 E2E flaky 時,改等待 status/DOM/network response,使用 Playwright auto-retry,不加任意秒數。trace、screenshot、console/network log 需 redaction token/prompt。 production regression 時,以 feature flag 回前一 prompt/model/route,停用高風險 tool,保存版本/dataset/eval evidence。修復後先跑 affected slice,再 full eval 與 canary。 驗證指令 npm run test:run npm run eval -- --dataset evals/golden.jsonl npx playwright test npm run build CI report 分開呈現 unit/contract、eval slices、live integration 與 E2E,不能用一個綜合分數掩蓋 critical failure。驗收加上 prompt injection、PII redaction、tool denial、duplicate mutation、timeout/cancel、offline/error UI 與 mobile keyboard。 官方來源 OpenAI Evaluation best practices Playwright Assertions Vitest Mocking Requests 延伸閱讀 在 技術文章 查看 AI roadmap 與 agent permissions。 從 課程總覽 建立分層 AI test harness。 可由 聯絡頁 提供 redacted failing case。