Communitygithub.com

lguidolin/tests-as-a-control

用於編寫或修改測試時、重構過程中測試失敗時,或測試權限/角色規則時。徵狀包括—想要修改測試以讓它通過、只測試快樂路徑、原本應該拒絕的測試突然通過、不穩定的測試,或不確定該斷言什麼。

tests-as-a-control 是什麼?

此技能將測試視為「對照標本」:測試固定住一個預期結果,使得行為的任何變化都會變得可見。核心紀律是「不要為了吻合程式碼而修改測試;要理解為什麼它們不一致。」它明確區分行為變更(應遵循測試先行)與結構變更(應以不修改測試的現有測試套件驗證)。此技能提供一套嚴格的規則,涵蓋:公開重新命名會跨越合約邊界,因此應視為行為變更;重構時絕不允許修改測試,只有需求變更時才能刻意、可審查地更新測試;權限/角色規則必須以 (角色 × 動作 × 資源) 矩陣方式雙向測試(允許與拒絕);不穩定的測試必須立即隔離,不可忽視。它也包含常見的合理化藉口與其對應的現實,以及應立即停止的危險信號。

相容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lguidolin/agent-skills/tree/main/skills/tests-as-a-control

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

Tests as a Control (測試作為對照標本)

概述

測試是一個對照標本:它固定住一個預期結果,使得行為的任何變化都變得可見。紀律是:不要修正測試來吻合程式碼;要理解它們為什麼不一致。

規則

  • 行為變更時測試先行。 先寫出會失敗的測試,觀察它以正確的理由失敗,然後寫出最少量的程式碼讓它通過。如果你沒有觀察到它失敗,你就不知道它測試的是你預期的內容。這個循環本身是 superpowers:test-driven-development;本技能管轄的是測試必須包含什麼,以及一旦測試存在,失敗代表什麼意義。無論是否安裝了該技能,此規則都適用。

  • 結構變更由未修改的測試套件驗證。 純粹的結構變更—重新命名、移動、拆分、提取,且無可觀察的行為變更—不需要新的測試。它的通過條件正好相反:整個現有的測試套件在未編輯任何測試的情況下通過。 這就是為什麼測試先行在此不適用—因為沒有行為需要指定,所以沒有測試能有意義地先失敗。如果一個結構變更迫使你編輯測試,那麼它就不是純粹的結構變更。停止並使用下一條規則重新分類。

  • 跨越合約邊界的重新命名不是重構。 問題不在於「程式碼是否改變了行為?」而在於「這個名稱對於我不控制的東西來說是可觀察的嗎?」

    重新命名的項目分類通過條件
    內部符號、私有輔助函式、本地檔案結構變更現有測試套件未經編輯即通過
    匯出的函式、API/GraphQL 欄位、路由、CLI 標誌、資料庫欄位、事件名稱、設定鍵行為變更—它會破壞消費者測試先行 + 新增並棄用

    公開重新命名對相同的資料執行相同的操作,但仍然是一個破壞性變更,因為名稱本身就是合約(Hyrum's Law — 請參閱 performance-and-scale)。將其視為行為變更:透過測試先行循環新增新名稱,在棄用視窗期間保留舊名稱及其通過的測試,只有在消費者遷移後才移除舊名稱。

    第三種情況是測試中以字串形式引用名稱—快照、DI 容器鍵、反射、fixture 路徑。這些在內部重新命名時會中斷。這不是規則失效;而是測試與實作細節耦合的訊號。修復耦合,不要重新基線化。

  • 重構不得編輯測試;需求變更才可以。 不同的行為:

    • 重構導致測試失敗 → 測試發現了非預期的行為變更。停下來並理解它。 為了讓重構通過而編輯測試,會毀掉你的儀器。
    • 需求真正改變了 → 刻意地更新測試,作為其自身可審查的變更,並如實記錄。
    • 禁止的行為是默默地重新基線化。
  • 對稱覆蓋 — 每個規則都需雙向測試。 證明授權的行為者能夠 以及 未授權的行為者不能。僅測試快樂路徑只測試了半條規則。

  • 權限/角色規則是個矩陣: 每個有意義的 (角色 × 動作 × 資源) 單元格,都需測試允許和拒絕。一個新角色在其拒絕案例存在之前是不完整的。一個原本應該拒絕的測試突然通過,是一個安全性回歸—必須明確審查並接受,絕不重新基線化。

  • 進入點絕不能默默地新增/移除。 路由、命令、端點都需要存在性與冒煙測試覆蓋,這樣新增/移除動作會強制測試變更。

  • 按規模測試: 小型(純邏輯,無 I/O,確定性)、中型(真實資料庫/行程,隔離環境)、大型(完整堆疊)。多數小型,較少中型,最少大型。留意缺失的中間地帶—伺服器/應用程式邏輯既未被單元測試也未被整合測試覆蓋,因為每一層都假設另一層會測試它。

  • 不穩定的測試一經發現立即隔離。 在沒有程式碼變更的情況下時而通過、時而失敗的測試會破壞對整個測試套件的信任。立即隔離(移出阻斷閘門,提交修復工單)—絕不留下間歇性失敗的測試,也絕不默默地刪除。小型測試必須是確定性的:不應使用真實時鐘、隨機性或網路。

  • 測試程式碼應是 DAMP,而非 DRY。 Descriptive And Meaningful Phrases(描述性且有意義的短語) 優於去重複化。測試必須能在隔離狀態下被直觀理解。將 DRY 應用於測試是一種違規,而非美德(請參閱 change-hygiene-and-code-craft)。

常見的合理化藉口

藉口現實
「我先寫程式碼,然後再補測試」那麼測試就是為了配合程式碼,而非配合需求。你從未觀察到它失敗,所以你不知道它是否真的在測試任何東西。
「只是重新命名,不需要測試」正確—如果名稱是內部的話。如果名稱是匯出/公開的,那麼名稱本身就是合約,重新命名就是行為變更。
「我重新命名了它,然後不得不修正三個測試,這很正常」這不正常。要麼你跨越了合約邊界,要麼那些測試與實作細節耦合。重新分類。
「測試現在錯了,我只要更新它就行了」只有當需求改變時才能這麼做。如果你正在重構,測試發現了真正的變更—請調查。
「我測試了快樂路徑,這樣就夠了」在拒絕案例也測試過之前,一條規則不算被測試過。
「這個拒絕測試現在通過了,太好了,快發布」一個被禁止的動作成功了是安全性回歸,不是進展。
「測試不穩定,只要重新執行 CI 就好」不穩定性會掩蓋真正的錯誤,並讓每個人都學會忽略失敗。隔離並修復。
「讓我用一個共享輔助函式來 DRY 這些測試」測試是 DAMP 的。有助於原地清晰度的重複應該保留。

危險信號 — 立即停止

  • 在行為變更的失敗測試存在之前,就先撰寫生產程式碼
  • 一個結構變更的 diff 同時觸及了測試檔案
  • 原地重新命名公開/匯出的名稱,而非使用新增並棄用策略
  • 在重構過程中編輯測試以讓它變綠
  • 新的權限沒有拒絕案例的測試
  • 在未理解快照/斷言為何改變的情況下重新基線化
  • 包含真實 sleep、Date.now()、隨機資料或網路呼叫的測試
  • 為了消除重複而犧牲可讀性的測試程式碼重構

完整理由說明:憲法第四條,收錄於 engineering-constitution/references/engineering-constitution.md。關於 GraphQL/路由合約的具體細節,請參閱 graphql-contract-testing。

Individual skills in this repo

This repo contains 18 individual skills — each has its own dedicated page.

lguidolin/change-hygiene-and-code-craft

Use when writing or refactoring code, structuring a commit or PR, or deciding whether to abstract duplication. Symptoms — mixing reorg with logic changes, a PR doing several things at once, a file growing large, the second copy of similar code, or unsure whether to DRY something up.

lguidolin/cloud-delivery-aks

Use when deploying to Kubernetes or Azure Kubernetes Service (AKS), configuring cloud secrets, setting up progressive rollout/canary, per-PR ephemeral environments, or k8s health probes. Keywords — Kubernetes, AKS, Key Vault, Argo Rollouts, Flagger, canary, blue-green, liveness, readiness, PodDisruptionBudget, HPA, rollback, GHCR.

lguidolin/commit-history-rewrite

Use when an existing repository has messy commit history that needs to conform to conventional commits before adopting release-please, or when intermediate WIP/fixup/merge commits need to be cleaned up.

lguidolin/conventional-commits-and-releases

Use when committing, writing a commit message, opening a PR that will be squash-merged, or configuring automated versioning/changelogs. Keywords — conventional commits, release-please, semver, feat/fix/chore, breaking change, changelog.

lguidolin/defense-in-depth-security

Use when handling untrusted input, secrets, authentication/authorization, or dependencies — or threat-modeling a new surface. Keywords — STRIDE, threat model, least privilege, secrets management, supply chain, dependency scanning, input validation, audit log, defense in depth.

lguidolin/designing-before-building

Use when starting a feature, fixing a non-trivial bug, or about to write implementation code — before any code exists. Symptoms you need this: "this is simple, I'll just code it", reaching for the editor before a design is approved, or an idea that hasn't been turned into a spec and plan.

lguidolin/engineering-constitution

Use when starting work in a project that follows the engineering constitution, orienting to its rules, or deciding which engineering practice applies to a task — spec writing, commits, testing, security, deploys, database, or UI work.

lguidolin/graphql-contract-testing

Use when writing a GraphQL query/mutation that the UI and a test will share, or building route/schema contract or smoke tests. Symptoms — copying a query into a test, a test asserting on query text, schema change that didn't break the UI build, or RLS/permission drift. Keywords — graphql-codegen, typed document, contract test, route smoke test.

lguidolin/init-repo-CI

Use when setting up a new repository with conventional commits, release-please, and CI automation, or when retrofitting an existing repository that lacks automated versioning and PR validation workflows.

lguidolin/interface-craft-and-accessibility

Use when building or styling UI — components, layouts, forms, design tokens — or making accessibility decisions. Keywords — a11y, WCAG, keyboard navigation, focus state, contrast, design system, minimalist UI, component reuse, ARIA, semantic HTML.

lguidolin/merge-gates-and-automation

Use when setting up or changing CI, pre-push hooks, or a task runner, or deciding what must pass before merge. Symptoms — tempted to put authoritative checks only in a local hook, skip CI, bypass with --no-verify, or unsure what gates a merge vs. runs locally.

lguidolin/observability-and-slos

Use when adding logging, metrics, tracing, health checks, SLOs, or alerting — or when building a service surface that needs to be operable and debuggable. Keywords — structured logs, OpenTelemetry, correlation id, RED metrics, liveness, readiness, SLI, SLO, error budget, alerting.

lguidolin/performance-and-scale

Use when working on hot paths, list endpoints, pagination, data-access in loops, or public interfaces/schemas. Symptoms — unbounded queries, N+1 access, no latency budget, optimizing without measuring, or changing an interface many consumers depend on. Keywords — pagination, N+1, Hyrum's Law, performance budget, bundle size.

lguidolin/postgres-postgraphile-rls-and-sql

Use when writing PostgreSQL, PostGraphile config, Row-Level Security policies, SQL schema files, or working on the Browser→App→PostGraphile→Postgres data path. Keywords — RLS, SECURITY DEFINER, search_path, pgSettings, grants, roles, GraphQL depth limit, query cost, statement_timeout, SQL file organization.

lguidolin/recording-decisions

Use when a design or architecture decision has been made and needs to be captured — writing a decision record or ADR, updating a decision index, noting a deferred idea, or superseding a past decision. Keywords — ADR, decision record, rationale, rejected alternatives, dependency index.

lguidolin/resilience-and-deploy-safety

Use when planning a deploy, designing a rollback, or responding to an incident or writing a postmortem. Keywords — deploy safety, rollback, immutable artifact, progressive delivery, canary, blast radius, incident response, blameless postmortem, error budget.

lguidolin/ship-it

Use when the user wants to ship work — push, PR, archive decision records, merge, and clean up. Handles the full lifecycle from committing final changes through post-merge cleanup including converting specs/plans to compact decision records.

lguidolin/zero-downtime-migrations

Use when changing a database schema where data must survive the change — adding/removing/renaming columns, constraints, indexes, or backfilling. Symptoms — a destructive migration bundled with a code deploy, a NOT NULL column with a backfill, a table-locking UPDATE, or a rename. Keywords — expand/contract, parallel change, backfill, NOT VALID, CREATE INDEX CONCURRENTLY, graphile-migrate.

相關技能