Communitygithub.com

lguidolin/tests-as-a-control

테스트를 수정하거나 작성할 때, 리팩터링 중 테스트가 실패할 때, 또는 권한/역할 규칙을 테스트할 때 사용하세요. 증상: 테스트를 통과시키기 위해 테스트를 수정하고 싶은 유혹, 해피 패스만 테스트, 갑자기 통과하기 시작한 거부 테스트, 불안정한 테스트, 또는 무엇을 단언(assert)해야 할지 확신이 없음.

tests-as-a-control란 무엇인가요?

이 스킬은 테스트를 **제어 표본(control specimen)** 으로 취급하는 원칙을 가르칩니다. 즉, 테스트는 기대치를 고정시켜 행동 변화를 가시화하며, "테스트를 코드에 맞추지 말고, 왜 불일치하는지 이해하라"는 규율이 핵심입니다. 구조적 변경(리팩터링)은 기존 테스트 스위트가 수정 없이 통과해야 하며, 동작 변경은 반드시 실패하는 테스트를 먼저 작성한 후 코드를 개발합니다. 특히 공개 API, GraphQL 필드, 라우트, CLI 플래그 등 계약 경계(contract boundary)를 넘는 이름 변경은 동작 변경으로 분류하여 추가 후 폐기(add-and-deprecate) 방식을 따라야 합니다. 또한 권한/역할 규칙은 (역할 × 액션 × 리소스) 매트릭스의 모든 셀에 대해 허용(allow)과 거부(deny)를 대칭적으로 테스트해야 하며, 불안정한 테스트는 즉시 격리하고, 테스트 코드는 DRY보다 DAMP(Descriptive And Meaningful Phrases)를 우선하여 가독성을 높여야 합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/lguidolin/agent-skills/tree/main/skills/tests-as-a-control

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

테스트 — 제어 표본(Control)

개요

테스트는 제어 표본입니다. 즉, 기대값을 고정시켜 행동의 변화가 눈에 띄게 만듭니다. 핵심 규율은 테스트를 코드에 맞추지 말고, 왜 서로 다른지 이해하라는 것입니다.

규칙

  • 동작 변경은 테스트 우선(Test-first). 실패하는 테스트를 먼저 작성하고, 올바른 이유로 실패하는지 확인한 후, 통과하는 최소한의 코드를 작성하십시오. 실패를 보지 않았다면, 당신이 생각한 대로 테스트하는지 알 수 없습니다. 이 루프 자체는 superpowers:test-driven-development 스킬에서 다루며, 이 스킬은 테스트가 무엇을 포함해야 하는지와 일단 테스트가 존재할 때 실패가 무엇을 의미하는지를 규율합니다. 해당 스킬이 설치되었는지 여부와 관계없이 이 규칙은 유효합니다.

  • 구조적 변경은 변경되지 않은 스위트로 검증합니다. 순수 구조적 변경(이름 변경, 이동, 분할, 추출 — 관찰 가능한 동작 변화 없음)에는 새 테스트가 필요하지 않습니다. 그 관문은 반대입니다. 기존 전체 스위트가 수정된 테스트 없이 모두 통과해야 합니다. 이것이 테스트 우선이 여기에 적용되지 않는 이유입니다 — 지정할 동작이 없으므로 어떤 테스트도 의미 있게 먼저 실패할 수 없습니다. 구조적 변경이 테스트 편집을 강제한다면, 그것은 구조적 변경이 아니었습니다. 멈추고 다음 규칙을 사용하여 재분류하십시오.

  • 계약 경계를 넘는 이름 변경은 리팩터링이 아닙니다. 핵심 질문은 "코드가 동작을 바꾸었는가?"가 아니라 "이 이름이 내가 제어하지 않는 무언가에게 관찰 가능한가?"입니다.

    변경된 대상분류관문
    내부 심볼, 비공개 헬퍼, 로컬 파일구조적기존 스위트가 수정 없이 통과
    내보낸 함수, API/GraphQL 필드, 라우트, CLI 플래그, DB 컬럼, 이벤트 이름, 설정 키동작적 — 소비자에게 영향테스트 우선 + 추가 후 폐기

    공개 이름 변경은 동일한 데이터에 동일한 작업을 수행하지만 여전히 호환성을 깨뜨리는 변경입니다. 이름이 계약이었기 때문입니다(히럼의 법칙 — performance-and-scale 참조). 동작적 변경으로 취급하십시오: 우선 실패하는 테스트 사이클로 새 이름을 추가하고, 이전 이름과 그 통과 테스트를 폐기 기간 동안 유지하며, 소비자가 마이그레이션한 후에만 이전 이름을 제거하십시오.

    세 번째 사례는 이름을 문자열로 참조하는 테스트입니다 — 스냅샷, DI 컨테이너 키, 리플렉션, 픽스처 경로 등. 이러한 것들은 내부 이름 변경 시 깨집니다. 이는 규칙이 실패한 것이 아니라, 테스트가 구현 세부 사항에 결합되었음을 알리는 신호입니다. 결합을 수정하고, 다시 기준을 맞추지 마십시오.

  • 리팩터링은 테스트를 수정해서는 안 되며, 요구사항 변경만 수정해야 합니다. 서로 다른 행위입니다:

    • 리팩터링이 테스트를 깨뜨렸다 → 테스트가 의도하지 않은 동작 변경을 포착한 것입니다. 멈추고 이해하십시오. 리팩터링을 통과시키기 위해 테스트를 수정하는 것은 계측을 파괴하는 행위입니다.
    • 요구사항이 실제로 변경되었다 → 테스트를 의도적으로, 별도의 검토 가능한 변경으로 업데이트하고, 그렇게 기록하십시오.
    • 금지된 행위는 조용히 기준을 다시 맞추는 것입니다.
  • 대칭적 커버리지 — 모든 규칙을 양방향으로 테스트하십시오. 인가된 주체가 할 수 있음(allow) 을 증명하고, 인가되지 않은 주체가 할 수 없음(deny) 을 증명하십시오. 해피 패스만 테스트하는 것은 규칙의 절반만 테스트하는 것입니다.

  • 권한/역할 규칙은 매트릭스입니다: 모든 의미 있는 (역할 × 액션 × 리소스) 셀에 대해 허용(allow)과 거부(deny) 를 테스트하십시오. 새로운 역할은 거부(deny) 사례가 존재할 때까지 불완전합니다. 갑자기 통과하게 된 거부 테스트는 보안 회귀(security regression) 입니다 — 명시적으로 검토하고 수용하며, 절대 기준을 다시 맞추지 마십시오.

  • 진입점(Entry points)은 조용히 추가/제거되지 않습니다. 라우트, 명령어, 엔드포인트 각각에 존재 여부와 스모크 커버리지를 포함하여 추가/제거가 테스트 변경을 강제하도록 하십시오.

  • 크기별 테스트: 소형(small) (순수 로직, I/O 없음, 결정적), 중형(medium) (실제 DB/프로세스, 격리된 환경), 대형(large) (전체 스택). 많은 소형, 적은 중형, 가장 적은 대형. 누락된 중간(missing middle) 을 주시하십시오 — 서버/애플리케이션 로직이 단위 테스트나 통합 테스트 모두 다루지 않는 부분으로, 각 계층이 다른 계층이 테스트한다고 가정하기 때문입니다.

  • 불안정한 테스트(Flaky tests)는 발견 즉시 격리합니다. 코드 변경 없이 통과/실패하는 테스트는 전체 스위트에 대한 신뢰를 파괴합니다. 즉시 격리하십시오(차단 게이트에서 제외하고, 수정을 위한 이슈 등록) — 간헐적으로 실패하게 두지 말고, 조용히 삭제하지 마십시오. 소형 테스트는 결정적이어야 합니다: 실제 시계, 무작위성, 네트워크가 없어야 합니다.

  • 테스트 코드는 DAMP이지 DRY가 아닙니다. Descriptive And Meaningful Phrases(설명적이고 의미 있는 구문)가 중복 제거보다 우선합니다. 테스트는 분리되어 읽혀도 명확해야 합니다. 테스트에 DRY를 적용하는 것은 위반이지 미덕이 아닙니다(change-hygiene-and-code-craft 참조).

일반적인 합리화

변명현실
"코드를 먼저 작성한 다음 테스트를 추가하겠습니다"그러면 테스트가 요구사항이 아닌 코드에 맞춰진 것입니다. 실패하는 것을 본 적이 없으므로, 그것이 무엇을 테스트하는지 알 수 없습니다.
"단순한 이름 변경이라 테스트가 필요 없습니다"맞습니다 — 이름이 내부적이라면 말이죠. 내보내졌거나 공개된 경우, 이름이 계약이었으며 이름 변경은 동작적 변경입니다.
"이름을 바꾸고 세 개의 테스트를 수정해야 했는데, 정상입니다"정상이 아닙니다. 계약 경계를 넘었거나, 해당 테스트들이 구현 세부 사항에 결합되어 있었습니다. 재분류하십시오.
"테스트가 이제 틀렸으니 업데이트만 하겠습니다"요구사항이 변경된 경우에만 해당됩니다. 리팩터링 중이라면 테스트가 실제 변경을 포착한 것입니다 — 조사하십시오.
"해피 패스를 테스트했으니 충분합니다"거부 사례도 테스트될 때까지 규칙은 테스트된 것이 아닙니다.
"이 거부 테스트가 이제 통과하네요, 좋아요, 배포합니다"금지된 동작이 성공하는 것은 보안 회귀이지 진전이 아닙니다.
"테스트가 불안정하니 CI를 다시 실행하세요"불안정성은 실제 버그를 감추고 모두가 실패를 무시하는 습관을 들입니다. 격리하고 수정하십시오.
"공유 헬퍼로 테스트들을 DRY하게 만들겠습니다"테스트는 DAMP입니다. 제자리에서 명확성을 돕는 중복은 유지됩니다.

위험 신호 — STOP

  • 동작 변경에 대한 프로덕션 코드를 실패하는 테스트 없이 작성하는 경우
  • 구조적 변경의 diff가 테스트 파일도 건드리는 경우
  • 공개/내보낸 이름을 추가 후 폐기 대신 제자리에서 변경하는 경우
  • 리팩터링 중 테스트를 통과시키기 위해 수정하는 경우
  • 거부 사례 테스트가 없는 새 권한
  • 스냅샷/단언(assertion)을 왜 변경되었는지 이해하지 않고 다시 기준을 맞추는 경우
  • 실제 sleep, Date.now(), 무작위 데이터, 또는 네트워크 호출이 있는 테스트
  • 가독성을 희생하며 중복을 제거하기 위해 테스트 코드를 리팩터링하는 경우

전체 근거: 헌법 제4조, 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.

관련 스킬