tdd (Matt Pocock)
레드-그린-리팩터링과 통합 테스트를 통해 테스트를 먼저 작성하며 기능을 만들고 버그를 고칩니다.
mattpocock tdd: 프롬프트가 이미 테스트 우선을 요구한 경우 향상이 없었고, 토큰은 약 1.7배 사용했습니다
Codex CLI · gpt-6-astra
22.7 / 30
스킬 없이 대비 향상폭: +0.00 · 상황에 따라 · 18회 실행
상황에 따라: 향상폭 +0.00가 +1.0 미만. 추천이 되려면 총점 22/30, 출력 품질 3.5, 향상폭 +1.0이 필요합니다.
SOTA
통과Codex로 테스트
커버 대상 Codex, Claude Code
Codex CLI 0.166 (SOTA 티어): 채점된 실행 18회. 여기서 통과하면 티어 규칙에 따라 Claude Code도 커버되지만, 두 번째 티어에 대해서는 아무것도 말해주지 않습니다.
점수
각 차원은 0~5점이며 여섯 차원을 더하면 30점 만점의 총점이 됩니다. 모든 차원에서 5가 가장 좋습니다. 비용 5는 저렴하고 빠름, 설치 5는 쉬움을 뜻합니다.
- 트리거 신뢰도
- 5.0
- 스킬 없이 대비 향상폭
- 2.0
- 출력 품질
- 4.7
- 토큰 및 시간 비용
- 2.0
- 설치 및 이식성
- 4.0
- 안전성
- 5.0
보안 리뷰
안전정적 스캔: CRITICAL 0 / HIGH 0 / MEDIUM 0. 수동 검토: 네트워크가 차단된 샌드박스에서 실행 안전(발견 사항 없음). 스캔이 깨끗하다고 해서 스킬이 안전하다는 증거는 아닙니다.
설치
npx skills@latest add mattpocock/skills --skill tdd
고정 커밋: 4ae2317ddb22b046e26455e4f9c9b3aaaf62212d
테스트일: 2026-11-02
테스트한 과제
- 명세에 따라 함수 구현 — slug.py에 slugify(text, max_length=None)를 TDD로 구현하세요(Python 3, 표준 라이브러리만 사용). 명세: 텍스트를 소문자로 바꿉니다. ASCII 문자나 숫자가 아닌 문자가 연속된 구간은 하나의 '-'로 바꿉니다. 앞뒤의 '-'를 제거합니다. 결과가 비어 있으면…
- 작은 상태 저장 클래스 구현 — ratelimiter.py를 테스트 우선(레드-그린-리팩터) 방식으로 작성하세요(Python 3, 표준 라이브러리만 사용). 클래스는 RateLimiter(limit, window_seconds, clock=time.monotonic)이며, 키별 슬라이딩 윈도 리미터입니다. allow(key)는 호출 수가 limit보다 적으면 True를 반환하고 호출을 기록합니다…
- 반올림 버그 수정 — 고객들은 cart.py의 cart_total이 회계 시스템과 비교해 가끔 1센트 어긋난다고 합니다. 회계 시스템은 각 단계(각 라인 합계, 할인된 소계, 세금)에서 0.5를 올리는 방식으로 센트 단위 반올림합니다. 예를 들어 cart_total([(1.005, 1)])은 1.09를 반환해야 하는데…
장점
- 스킬 이름을 언급하지 않았는데도 적용되었습니다. 스킬 사용군 실행 6/6회에서 스킬 파일을 읽었습니다.
- 스킬 사용 시 높은 절대 품질: 4.67/5.
단점
- 스킬 없이보다 측정 가능한 향상 없음(+0.00, 노이즈 범위 안).
- 한 문장 프롬프트는 4.67점, 스킬 사용은 4.67점(+0.00).
- 스킬 없이보다 토큰을 1.7배 사용합니다.
- 테스트 참고: 한 문장 프롬프트군(6회 실행)은 대조군·스킬 사용군과 다른 머신에서 나중에 추가되었습니다.
- 과제·그룹당 n=2이며, 약 1점 미만의 차이는 노이즈입니다.
리뷰
Codex CLI 0.166 (gpt-6-astra, low reasoning)을 Mac 테스트 머신에서 실행했고, 실행마다 새 임시 프로젝트와 임시 HOME/TMPDIR/캐시를 사용했습니다. 스킬 설치(스킬 사용군), 미설치(대조군), 미설치 상태에서 스킬의 핵심 요구사항만 한 문장으로 압축해 프롬프트에 덧붙인 경우(한 문장 프롬프트군)로 나눴으며, 과제·그룹당 2회씩 실행했습니다. 네트워크는 끄고 /tmp 쓰기는 차단했습니다. 프롬프트에는 스킬 이름을 언급하지 않았습니다(아래에 적은 경우 제외). 이 스킬의 설명은 사용자가 TDD / 테스트 우선을 요청할 때만 트리거되므로 프롬프트에 그렇게 명시했습니다("TDD로 진행", "테스트 우선(레드-그린-리팩터)", "테스트 먼저 진행"). 대조군에도 같은 문구를 넣었기 때문에, 이 테스트는 TDD를 요청한 평범한 프롬프트 위에서 스킬이 얼마나 개선하는지를 봅니다.
3개 그룹 비교
| 그룹 | 평균 점수 (0-5) | 실행당 토큰 |
|---|---|---|
| 스킬 없음 | 4.67 | 86k |
| 한 문장 프롬프트 | 4.67 | 178k |
| 스킬 사용 | 4.67 | 146k |
Codex CLI에서 18회 실행해 측정했고, LLM 심사 없이 규칙 기반 검사로 채점했습니다. 총점 22.7 / 30, 판정 상황에 따라.
참고: 한 문장 프롬프트군(6회)은 나중에 다른 머신에서 추가되어 대조군·스킬 사용군과 환경이 다릅니다.
판정: 상황에 따라 다름 (22.7 / 30). 출처: mattpocock/skills, skills/engineering/tdd, 커밋 4ae2317에 고정. 설치: npx skills@latest add mattpocock/skills --skill tdd. 라이선스: MIT (저장소 LICENSE).
어떤 스킬인가
Matt Pocock의 skills 저장소(engineering/tdd)에 있는 tdd 스킬입니다. 레드-그린 루프와, 좋은 테스트란 무엇인지와 언제 목을 사용할지를 다룬 참고 파일 두 개가 있습니다. SKILL.md는 약 570단어이며 순수 Markdown이고 스크립트는 없습니다. 사용자가 테스트 우선 작업을 원할 때 트리거됩니다.
테스트 방법
Codex CLI 0.166(gpt-6-astra, 낮은 추론)을 Mac 테스트 머신에서 실행했고, 실행마다 새 임시 프로젝트와 임시 HOME/TMPDIR/캐시를 사용했습니다. 스킬을 설치한 경우(스킬 사용군), 설치하지 않은 경우(대조군), 스킬의 핵심 요구사항을 한 문장으로 압축해 프롬프트 끝에 붙인 경우(한 문장 프롬프트군)로 나눴고, 작업마다 각 군 2회씩 실행했습니다. 네트워크는 끄고 /tmp 쓰기는 차단했습니다. 프롬프트에는 스킬 이름을 전혀 언급하지 않았습니다(아래에 적은 표현은 예외).
이 스킬의 설명은 사용자가 TDD / 테스트 우선을 요청할 때만 트리거되므로, 프롬프트에 그렇게 적었습니다("using TDD", "test-first (red-green-refactor)", "Do it test-first"). 같은 표현을 대조군에도 주었으므로, 이 테스트는 스킬이 평범한 TDD 요청을 개선하는지를 봅니다.
- 정확한 명세로 함수 구현 — TDD를 사용해 slug.py에
slugify(text, max_length=None)를 구현하세요(Python 3, 표준 라이브러리만). 명세: 텍스트를 소문자로; ASCII 문자나 숫자가 아닌 문자의 연속 구간을 하나의 '-'로 바꿈; 앞뒤 '-' 제거; 빈 결과… - 작은 상태 저장 클래스 구현 —
RateLimiter(limit, window_seconds, clock=time.monotonic)클래스로 ratelimiter.py(Python 3, 표준 라이브러리만)를 테스트 우선(레드-그린-리팩터)으로 작성하세요. 키별 슬라이딩 윈도 리미터입니다.allow(key)는 호출 수가limit보다 적으면 True를 반환하고 호출을 기록… - 반올림 버그 수정 — 고객은 cart.py의
cart_total이 회계 시스템과 비교해 가끔 1센트 어긋난다고 합니다. 회계 시스템은 각 단계(각 라인 합계, 할인된 소계, 세금)에서 센트 단위로 반올림(half-up)합니다. 예를 들어 cart_total([(1.005, 1)])은 1.09를 반환해야 하…
채점은 프로그램 방식(숨은 테스트, 심어 둔 문제 탐지, 제약 조건 검사, 해당되는 경우 렌더링+OCR)으로 실행당 0~5점입니다. 점수를 결정한 LLM 심사자는 없습니다.
결과
세 번째 군에 전달한 한 문장 프롬프트: "수직 슬라이스로 테스트 우선 작업을 하세요. 공개 인터페이스를 통한 실패하는 테스트를 하나 작성하고, 실패를 확인하고, 통과할 최소한의 코드를 작성하고, 반복하되 그린일 때만 리팩터하세요. 테스트를 한꺼번에 미리 작성하지 마세요."
| 작업 | 대조군 점수 | 한 문장 프롬프트 | 스킬 사용 | 대조군 토큰 | 한 문장 프롬프트 토큰 | 스킬 사용 토큰 | 스킬 읽음 |
|---|---|---|---|---|---|---|---|
| t1-slugify | 5.0 | 5.0 | 5.0 | 79k | 184k | 135k | 2/2 |
| t2-ratelimiter | 5.0 | 5.0 | 5.0 | 99k | 183k | 169k | 2/2 |
| t3-cart-bugfix | 4.0 | 4.0 | 4.0 | 79k | 164k | 133k | 2/2 |
평균 점수: 스킬 없이 4.67, 스킬 사용 시 4.67(델타 +0.0). 실행당 토큰: 86k 대 146k(1.70배), 시간 50초 대 67초. 한 문장 프롬프트군: 4.67(한 문장 프롬프트 대비 스킬의 이득: +0.0, 스킬 없는 경우 대비 한 문장 프롬프트의 이득: +0.0). 한 문장 프롬프트는 실행당 178k 토큰을 쓰므로, 스킬은 한 문장 프롬프트의 0.82배 비용입니다.
실제로 있었던 일
- 프롬프트가 이미 테스트 우선을 요구했기 때문에 대조군도 그렇게 했습니다. 두 구현 작업에서는 두 군의 모든 실행이 '구현 전에 테스트 작성'을 포함해 모든 검사를 통과했습니다. 스킬은 거기서 측정 가능한 것을 더하지 않았습니다.
- 버그 수정 작업(장바구니 반올림)에서는 스킬 유무와 관계없이 네 번의 실행 모두 버그를 고치고 숨은 테스트를 통과했지만, 네 번 모두 같은 프로세스 검사에서 실패했습니다. 실패하는 테스트를 먼저 두는 대신 수정이나 테스트가 다른 하나와 같은 단계에 도착했습니다. 스킬은 그것을 바꾸지 못했습니다.
- 스킬은 스킬 사용군 6/6 실행에서 읽혔습니다. 같은 결과를 위해 에이전트가 훨씬 더 많은 작업을 하게 만들었습니다(구현 작업에서 명령 24~26회 대 8~10회). 토큰은 약 1.7배입니다.
- 숨은 테스트 정확도는 두 군 모두 100%였습니다.
- 한 문장 버전(수직 슬라이스로 테스트 우선, 한 번에 실패하는 테스트 하나): 평균 점수 4.67로 대조군·스킬과 동일했고, 실행당 178k 토큰으로 대조군 86k(2.1배), 스킬 146k를 썼습니다. 한 문장 프롬프트 에이전트는 테스트 루프를 반복해서 실행해 평균 27개 명령을 실행한 반면, 대조군은 9개, 스킬은 23개였습니다.
- 모든 군이 모든 실행에서 자체 테스트를 작성하고 숨은 테스트를 모두 통과했습니다. 구현 전 테스트는 세 군 모두 6회 중 4회에서 충족되었으므로, 프롬프트 수준의 '테스트 우선' 요청이 이미 스킬의 목적을 달성했습니다.
항목별 점수
| 항목 | 점수 | 이유 |
|---|---|---|
| 1_output_quality | 4.67 | 스킬 사용 시 평균 4.67/5점(프로그램 검사, 작업별 표 참조). |
| 2_lift_vs_no_skill | 2.0 | 스킬 없이 4.67점 대비 +0.00점(작업당 n=2, 약 1점 미만의 차이는 노이즈). |
| 3_trigger | 5.0 | 스킬 사용군 6/6 실행에서 스킬 파일이 읽힘. |
| 4_instructions_portability | 4 | |
| 5_efficiency | 2 | 토큰 1.70배(실행당 146k 대 86k), 시간 1.34배(67초 대 50초). |
| 6_safety_hygiene | 5 |
이 테스트의 한계
- 각 테스트 프롬프트 자체가 TDD를 요구해 대조군에 이득의 대부분을 주었습니다. 요청하지 않는 사용자라면 스킬 설명이 아예 트리거되지 않을 것입니다.
- 작은 Python 작업만 사용했습니다. 테스트 품질은 채점하지 않았습니다.
- 작업·군당 n = 2: 약 1점 미만의 차이는 노이즈입니다.
- 규칙 기반 채점은 검사가 볼 수 있는 것만 측정합니다. 취향이나 결과가 최선인지는 판단할 수 없습니다.
보안
security.md를 참조하세요. 스캔: CRITICAL 0 / HIGH 0 / MEDIUM 0. 판정: 실행해도 안전(네트워크 비활성 샌드박스). 깨끗한 스캔 결과가 스킬의 안전성을 증명하지는 않습니다.
결론
테스트 우선 요청 위에서는 이 스킬이 작은 작업에서 추가 이득을 내지 못합니다. 대조군, 한 문장 버전, 스킬이 모두 4.67점이었고, 스킬은 대조군의 1.7배, 한 문장은 2.1배 토큰을 썼습니다. 더 긴 세션에서 테스트를 동작 중심으로 유지하는 데는 도움이 될 수 있지만, 그 부분은 측정하지 않았습니다.
트랜스크립트
실행 로그: tdd (Codex CLI, network off, per-run temp HOME/TMPDIR, one row per graded run.) 열: 작업, 그룹-회차, 점수(0-5), 토큰, 초, 통과한 검사, 에이전트가 연 스킬 파일. t1-slugify control-1: 점수 5.0, 토큰 78904, 초 50.3, 검사 3/3, 연 파일 없음 t1-slugify control-2: 점수 5.0, 토큰 79205, 초 47.1, 검사 3/3, 연 파일 없음 t1-slugify oneliner-1: 점수 5.0, 토큰 199600, 초 74.7, 검사 3/3, 연 파일 없음 t1-slugify oneliner-2: 점수 5.0, 토큰 168650, 초 68.2, 검사 3/3, 연 파일 없음 t1-slugify treatment-1: 점수 5.0, 토큰 144538, 초 57.6, 검사 3/3, 연 파일 tdd t1-slugify treatment-2: 점수 5.0, 토큰 126047, 초 50.7, 검사 3/3, 연 파일 tdd t2-ratelimiter control-1: 점수 5.0, 토큰 99439, 초 60.2, 검사 3/3, 연 파일 없음 t2-ratelimiter control-2: 점수 5.0, 토큰 99611, 초 54.3, 검사 3/3, 연 파일 없음 t2-ratelimiter oneliner-1: 점수 5.0, 토큰 159969, 초 70.9, 검사 3/3, 연 파일 없음
18회 실행, 아티팩트 3개
샘플 출력
SOTA · With the skill: test_slug.py (t1-slugify, run 1, Codex)
import unittest from slug import slugify class SlugifyTests(unittest.TestCase): def test_hard_cuts_first_word_when_it_exceeds_limit(self): self.assertEqual(slugify('Supercalifragilistic', 5), 'super') def test_truncates_at_word_boundary(self): self.assertEqual(slugify('Hello, World!', 8), 'hello') def test_normalizes_text(self): self.assertEqual(slugify('Hello, World!'), 'hello-world') def test_preserves_only_ascii_letters_and_digits(self): self.assertEqual(slugify(' -- CAFÉ_42\t中文 / Test!! '), 'caf-42-test') def test_lowercases_before_filtering(self): self.assertEqual(slugify('\u212aELVIN'), 'kelvin') def test_keeps_slug_that_fits_limit(self): for limit in (11, 20, None): with self.subTest(limit=limit): self.assertEqual(slugify('Hello, World!', limit), 'hello-world') def test_keeps_complete_words_without_trailing_separator(self): for limit, expected in ((3, 'one'), (4, 'one'), (6, 'one'), (7, 'one-two'), (8, 'one-two')): with self.subTest(limit=limit): self.assertEqual(slugify('one two three', limit), expected) def test_hard_cuts_long_first_word_even_with_later_words(self): self.assertEqual(slugify('Supercalifragilistic hello', 5), 'super') def test_applies_limit_to_fallback(self): self.assertEqual(slugify('', 2), 'n') def test_empty_normalized_text_has_fallback(self): for text in ('', ' !!! ', '中文'): with self.subTest(text=text): self.assertEqual(slugify(text), 'n-a') if __name__ == '__main__': unittest.main()