test-driven-development (obra/superpowers)
실패하는 테스트를 먼저 작성한 뒤 구현 코드를 쓰는 테스트 우선 방식으로 기능을 만들고 버그를 고칩니다.
superpowers-tdd: 에이전트가 테스트를 먼저 작성하게 만들지만 토큰을 약 1.6배 사용합니다.
Codex CLI · gpt-6-astra
24.0 / 30
스킬 없이 대비 향상폭: +1.00 · 추천 · 18회 실행
추천: 총점 24.0/30이 22점 이상, 출력 품질 5.0가 3.5 이상, 향상폭 +1.00가 +1.0 이상입니다.
SOTA
통과Codex로 테스트
커버 대상 Codex, Claude Code
Codex CLI 0.166 (SOTA 티어): 채점된 실행 18회. 여기서 통과하면 티어 규칙에 따라 Claude Code도 커버되지만, 두 번째 티어에 대해서는 아무것도 말해주지 않습니다.
점수
각 차원은 0~5점이며 여섯 차원을 더하면 30점 만점의 총점이 됩니다. 모든 차원에서 5가 가장 좋습니다. 비용 5는 저렴하고 빠름, 설치 5는 쉬움을 뜻합니다.
- 트리거 신뢰도
- 5.0
- 스킬 없이 대비 향상폭
- 3.0
- 출력 품질
- 5.0
- 토큰 및 시간 비용
- 2.0
- 설치 및 이식성
- 4.0
- 안전성
- 5.0
보안 리뷰
안전정적 스캔: CRITICAL 0 / HIGH 0 / MEDIUM 0. 수동 검토: 네트워크가 차단된 샌드박스에서 실행 안전(발견 사항 없음). 스캔이 깨끗하다고 해서 스킬이 안전하다는 증거는 아닙니다.
설치
npx skills@latest add obra/superpowers --skill test-driven-development
고정 커밋: 2ce8aa15d05bd57213e6f746f953aafb7ded7a8b
테스트일: 2026-11-02
테스트한 과제
- 정확한 명세로 함수 구현 — slug.py에 slugify(text, max_length=None)를 구현하세요(Python 3, 표준 라이브러리만 사용). 명세: 텍스트를 소문자로 바꿉니다. ASCII 영문자나 숫자가 아닌 문자가 연속된 구간은 하나의 '-'로 바꿉니다. 앞뒤 '-'는 제거합니다. 결과가 비어 있으면 'n…
- 작은 상태 저장 클래스 구현 — ratelimiter.py(Python 3, 표준 라이브러리만 사용)에 class RateLimiter(limit, window_seconds, clock=time.monotonic)를 작성하세요. 키별 슬라이딩 윈도 제한기입니다. allow(key)는 최근 window_seconds 동안 기록된 호출이 limit개 미만이면 True를 반환하고 호출을 기록합니다…
- 반올림 버그 수정 — 고객들은 cart.py의 cart_total이 회계 시스템과 비교해 가끔 1센트 어긋난다고 합니다. 회계 시스템은 각 단계(각 라인 합계, 할인된 소계, 세금)에서 센트 단위로 반올림(half-up)합니다. 예를 들어 cart_total([(1.005, 1)])은 1.09를 반환해야 하며…
장점
- 스킬 없이보다 평균 점수를 4.0에서 5.0으로 끌어올렸습니다(+1.00).
- 스킬 이름을 언급하지 않았는데도 적용되었습니다. 스킬 사용군 실행 6/6회에서 스킬 파일을 읽었습니다.
- 자기 규칙을 한 문장으로 줄인 버전보다 +0.33점 높습니다.
- 스킬 사용 시 높은 절대 품질: 5.0/5.
단점
- 스킬 없이보다 토큰을 1.65배 사용합니다.
- 테스트 참고: 한 문장 프롬프트군(6회 실행)은 나중에 대조군·스킬 사용군과 다른 머신에서 추가한 그룹입니다.
- 과제·그룹당 n=2이며, 약 1점 미만의 차이는 노이즈입니다.
리뷰
Codex CLI 0.166 (gpt-6-astra, low reasoning)을 Mac 테스트 머신에서 실행했고, 실행마다 새 임시 프로젝트와 임시 HOME/TMPDIR/캐시를 사용했습니다. 스킬 설치(스킬 사용군), 미설치(대조군), 미설치 상태에서 스킬의 핵심 요구사항만 한 문장으로 압축해 프롬프트에 덧붙인 경우(한 문장 프롬프트군)로 나눴으며, 과제·그룹당 2회씩 실행했습니다. 네트워크는 끄고 /tmp 쓰기는 차단했습니다. 프롬프트에는 스킬 이름을 언급하지 않았습니다. 채점은 프로그램 기반(숨은 테스트 / 심어 둔 문제 회수 / 제약 조건 검사 / 해당되는 경우 렌더링+OCR)으로 실행당 0–5점입니다. 어떤 점수도 LLM 심사자가 결정하지 않았습니다.
3개 그룹 비교
| 그룹 | 평균 점수 (0-5) | 실행당 토큰 |
|---|---|---|
| 스킬 없음 | 4.0 | 65k |
| 한 문장 프롬프트 | 4.67 | 127k |
| 스킬 사용 | 5.0 | 108k |
Codex CLI에서 18회 실행해 측정했고, LLM 심사 없이 규칙 기반 검사로 채점했습니다. 총점 24.0 / 30, 판정 추천.
참고: 한 문장 프롬프트군(6회)은 나중에 다른 머신에서 추가되어 대조군·스킬 사용군과 환경이 다릅니다.
판정: 추천 (24.0 / 30). 출처: obra/superpowers, skills/test-driven-development, 커밋 2ce8aa1에 고정. 설치: npx skills@latest add obra/superpowers --skill test-driven-development. 라이선스: MIT(저장소 LICENSE).
무엇인가
obra/superpowers의 test-driven-development 스킬입니다. 설명문에는 "기능이나 버그 수정을 구현할 때, 구현 코드를 작성하기 전에 사용하세요"라고 되어 있습니다. 약 1,500단어의 Markdown(레드-그린-리팩터의 '철칙', 테스트를 건너뛰는 변명 표, 검증 체크리스트)과 좋은 테스트 작성에 관한 추가 파일 하나가 있습니다. 스크립트는 없습니다.
테스트 방법
Codex CLI 0.166(gpt-6-astra, 낮은 추론)을 Mac 테스트 머신에서 실행했고, 실행마다 새 임시 프로젝트와 임시 HOME/TMPDIR/캐시를 사용했습니다. 스킬을 설치한 스킬 사용군, 스킬이 없는 대조군, 스킬 없이 핵심 요구사항을 한 문장으로 압축해 프롬프트 끝에 붙인 한 문장 프롬프트군으로 나눴으며, 작업·군당 2회 실행, 네트워크 차단, /tmp 쓰기 차단 조건이었습니다. 프롬프트에는 스킬 이름을 전혀 언급하지 않았습니다.
- 정확한 명세로 함수 구현 — slug.py에
slugify(text, max_length=None)를 구현하세요(Python 3, 표준 라이브러리만). 명세: 텍스트를 소문자로 바꾸고, ASCII 문자나 숫자가 아닌 연속 문자를 하나의 '-'로 바꾸고, 앞뒤 '-'를 제거하고, 결과가 비면 'n… - 작은 상태 저장 클래스 구현 — ratelimiter.py(Python 3, 표준 라이브러리만)에
RateLimiter(limit, window_seconds, clock=time.monotonic)클래스를 작성하세요. 키별 슬라이딩 윈도 제한기입니다.allow(key)는 지난… 동안 기록된 호출이limit개 미만이면 True를 반환하고 호출을 기록합… - 반올림 버그 수정 — 고객들은 cart.py의
cart_total이 회계 시스템과 비교해 때때로 1센트 어긋난다고 합니다. 회계 시스템은 각 단계(각 라인 합계, 할인된 소계, 세금)에서 센트 단위로 반올림합니다. 예를 들어 cart_total([(1.005, 1)])은 1.09를 반환해야 하…
채점은 프로그램 방식(숨은 테스트/심어 둔 문제 탐지/제약 조건 검사/해당되는 경우 렌더링+OCR)으로 진행하며 실행당 0~5점입니다. 어떤 점수도 LLM 심사자가 결정하지 않았습니다.
결과
세 번째 군에 제공한 한 문장 프롬프트: "엄격한 레드-그린-리팩터를 따르세요. 먼저 실패하는 테스트를 작성하고 올바른 이유로 실패하는지 확인한 뒤, 통과시키는 최소 구현을 작성하고, 그다음 리팩터하세요. 실패하는 테스트 없이는 프로덕션 코드를 작성하지 마세요."
| 작업 | 대조군 점수 | 한 문장 프롬프트 | 스킬 사용 | 대조군 토큰 | 한 문장 토큰 | 스킬 사용 토큰 | 스킬 읽음 |
|---|---|---|---|---|---|---|---|
| t1-slugify | 4.0 | 5.0 | 5.0 | 62k | 134k | 108k | 2/2 |
| t2-ratelimiter | 4.0 | 5.0 | 5.0 | 63k | 147k | 97k | 2/2 |
| t3-cart-bugfix | 4.0 | 4.0 | 5.0 | 69k | 97k | 116k | 2/2 |
평균 점수: 스킬 없음 4.0 vs 스킬 사용 5.0(델타 +1.0). 실행당 토큰: 65k vs 108k(1.65배), 시간 55초 vs 51초. 한 문장 프롬프트군: 4.67(한 문장 프롬프트 대비 스킬의 이득: +0.33, 스킬 없음 대비 한 문장 프롬프트의 이득: +0.67). 한 문장 프롬프트는 실행당 127k 토큰이므로 스킬 비용은 한 문장 프롬프트의 0.85배입니다.
무슨 일이 있었나
- 정확성은 같았습니다. 두 군의 12회 실행 모두 숨은 테스트 스위트를 통과했습니다(테스트 12/12, 10/10, 11/11). 이런 작은 작업에서는 스킬이 코드의 작동 여부를 바꾸지 않습니다.
- 바꾸는 것은 작업 순서입니다. 스킬 사용군 6회 모두 테스트 파일이 구현보다 이른 단계에 작성되었습니다. 대조군 6회 모두 에이전트가 테스트와 구현을 같은 패치에 작성했고(또는 cart 작업에서 버그를 먼저 고친 뒤 테스트를 추가했고), 저희가 읽어 본 스킬 사용 실행(slugify)에서는 에이전트가 테스트를 작성하고, 실패하는지 확인하려고 실행한 뒤, 코드를 작성했습니다.
- 설명문만으로 트리거됩니다. 어떤 프롬프트도 TDD를 언급하지 않았지만 스킬 사용군 6/6 실행에서 스킬 파일이 읽혔습니다.
- 비용은 더 듭니다. 실행당 토큰이 약 1.65배(107k vs 65k)이고 실제 시간은 비슷했습니다(51초 vs 55초). 에이전트가 스킬과 동반 파일을 읽고 루프를 여러 번 실행하기 때문입니다.
- 한 문장 버전(엄격한 레드-그린-리팩터, 실패하는 테스트 없이는 프로덕션 코드 금지): 평균 4.67로 대조군(4.0)과 스킬(5.0) 사이였습니다. 실행당 127k 토큰으로 대조군 65k(2.0배), 스킬 108k(스킬의 1.2배)였습니다.
- 테스트가 구현보다 먼저 작성된 비율은 대조군 6회 중 0회, 한 문장 6회 중 4회, 스킬 6회 중 6회였습니다. 한 문장에서 빠진 두 실행은 cart 버그 수정 작업입니다. 스킬은 매번 그렇게 한 유일한 군이며, 한 문장(실행당 19개)보다 적은 명령(실행당 10개)으로 해냈습니다.
항목별 점수
| 항목 | 점수 | 이유 |
|---|---|---|
| 1_출력_품질 | 5.0 | 스킬 사용 시 평균 5.00/5(프로그램 검사, 작업별 표 참고). |
| 2_스킬_없음_대비_향상 | 3.0 | 스킬 없음 4.00 대비 +1.00점(작업당 n=2, 약 1점 미만은 노이즈). |
| 3_트리거 | 5.0 | 스킬 사용군 6/6 실행에서 스킬 파일을 읽었습니다. |
| 4_지침_이식성 | 4 | |
| 5_효율성 | 2 | 토큰 1.65배(실행당 108k vs 65k), 시간 0.93배(51초 vs 55초). |
| 6_안전성_및_위생 | 5 |
이 테스트의 한계
- 두 군 모두 숨은 테스트를 전부 통과했으므로(천장 효과) 측정된 향상은 결과 차이가 아니라 이벤트 로그에서 계산한 과정 검사(구현 전 테스트 작성)입니다.
- 작업은 작은 단일 파일 Python 연습문제입니다. 더 큰 저장소, 다른 언어, 테스트 품질(예: 뮤테이션 테스트)은 테스트하지 않았습니다.
- 작업·군당 n=2: 약 1점 미만의 차이는 노이즈입니다.
- 규칙 기반 채점은 검사가 볼 수 있는 것만 측정하며, 취향이나 결과가 최선인지는 판단하지 못합니다.
보안
security.md를 참고하세요. 스캔: CRITICAL 0 / HIGH 0 / MEDIUM 0. 판정: 실행해도 안전(네트워크 차단 샌드박스). 깨끗한 스캔 결과가 스킬의 안전성을 증명하지는 않습니다.
결론
말한 대로 합니다. 매번 실패하는 테스트를 먼저 작성한 유일한 군이며(6/6, 대조군 0/6, 한 문장 버전 4/6), 대조군 토큰의 1.7배, 한 문장 버전 토큰의 0.85배를 썼습니다. 이 작은 작업에서 최종 정확성은 모든 군이 같았으므로, 이득은 코드가 아니라 과정에 있습니다.
트랜스크립트
실행 로그: test-driven-development (Codex CLI, network off, per-run temp HOME/TMPDIR, one row per graded run.) 열: 작업, 그룹-회차, 점수(0-5), 토큰, 초, 통과한 검사, 에이전트가 연 스킬 파일. t1-slugify control-1: 점수 4.0, 토큰 62017, 초 55.5, 검사 2/3, 연 파일 없음 t1-slugify control-2: 점수 4.0, 토큰 62139, 초 53.8, 검사 2/3, 연 파일 없음 t1-slugify oneliner-1: 점수 5.0, 토큰 135535, 초 52.9, 검사 3/3, 연 파일 없음 t1-slugify oneliner-2: 점수 5.0, 토큰 134448, 초 46.7, 검사 3/3, 연 파일 없음 t1-slugify treatment-1: 점수 5.0, 토큰 118034, 초 56.3, 검사 3/3, 연 파일 test-driven-development t1-slugify treatment-2: 점수 5.0, 토큰 97974, 초 48.3, 검사 3/3, 연 파일 test-driven-development t2-ratelimiter control-1: 점수 4.0, 토큰 63363, 초 46.1, 검사 2/3, 연 파일 없음 t2-ratelimiter control-2: 점수 4.0, 토큰 63431, 초 98.9, 검사 2/3, 연 파일 없음 t2-ratelimiter oneliner-1: 점수 5.0, 토큰 140042, 초 60.8, 검사 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_normalizes_text(self): cases = [ ('Hello, World!', 'hello-world'), (' --ABC__123\t\nxyz!! ', 'abc-123-xyz'), ('café déjà vu 12٣', 'caf-d-j-vu'), ('İ X', 'i-x'), ('already-a-slug', 'already-a-slug'), ] for text, expected in cases: with self.subTest(text=text): self.assertEqual(slugify(text), expected) def test_empty_result_uses_fallback(self): for text in ('', '--- !\t', '中文', '😀'): with self.subTest(text=text): self.assertEqual(slugify(text), 'n-a') def test_length_limits_respect_word_boundaries(self): cases = [ ('Hello, World!', 8, 'hello'), ('Hello, World!', 11, 'hello-world'), ('Hello, World!', 20, 'hello-world'), ('Hello, World!', 5, 'hello'), ('Hello, World!', 6, 'hello'), ('one two three', 7, 'one-two'), ('one two three', 9, 'one-two'), ('Supercalifragilistic', 5, 'super'), ('Supercalifragilistic next', 5, 'super'), ('abc', 1, 'a'), ('abc', 0, ''), ('!!!', 1, 'n'), ('!!!', 2, 'n'), ('!!!', 3, 'n-a'), ] for text, limit, expected in cases: with self.subTest(text=text, limit=limit): self.assertEqual(slugify(text, limit), expected) def test_explicit_none_leaves_length_unlimited(self): self.assertEqual(slugify('Hello, World!', None), 'hello-world') if __name__ == '__main__': unittest.main()