一键导入
test-driven-development
TDD: enforce RED-GREEN-REFACTOR, tests before code.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
TDD: enforce RED-GREEN-REFACTOR, tests before code.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Orchestrate Daimon specialists in Aether Agents — delegation, monitoring, Pure Orchestrator pattern, cost optimization, and common pitfalls.
Delegate tasks to specialist Daimons — decompose, delegate, monitor, and report results.
Configure, extend, or contribute to Hermes Agent.
GitHub PR lifecycle: branch, commit, open, CI, merge.
Design and rework Aether Agents Daimon profiles — SOUL.md, config.yaml, toolset selection, and agent type taxonomy. Covers the full rework cycle from role definition to implementation.
Design HTML UI artifacts — from rapid multi-variant mockups (sketches) to polished single-artifact landing pages, decks, and prototypes. Includes design theology, anti-slop rules, and comparison workflows.
| name | test-driven-development |
| description | TDD: enforce RED-GREEN-REFACTOR, tests before code. |
| version | 1.1.0 |
| author | Hermes Agent (adapted from obra/superpowers) |
| license | MIT |
| metadata | {"hermes":{"tags":["testing","tdd","development","quality","red-green-refactor"],"related_skills":["systematic-debugging","writing-plans","subagent-driven-development"]}} |
Write the test first. Watch it fail. Write minimal code to pass.
Core principle: If you didn't watch the test fail, you don't know if it tests the right thing.
Violating the letter of the rules is violating the spirit of the rules.
Always:
Exceptions (ask the user first):
Thinking "skip TDD just this once"? Stop. That's rationalization.
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
Write code before the test? Delete it. Start over.
No exceptions:
Implement fresh from tests. Period.
Write one minimal test showing what should happen.
Good test:
def test_retries_failed_operations_3_times():
attempts = 0
def operation():
nonlocal attempts
attempts += 1
if attempts < 3:
raise Exception('fail')
return 'success'
result = retry_operation(operation)
assert result == 'success'
assert attempts == 3
Clear name, tests real behavior, one thing.
Bad test:
def test_retry_works():
mock = MagicMock()
mock.side_effect = [Exception(), Exception(), 'success']
result = retry_operation(mock)
assert result == 'success' # What about retry count? Timing?
Vague name, tests mock not real code.
Requirements:
MANDATORY. Never skip.
# Use terminal tool to run the specific test
pytest tests/test_feature.py::test_specific_behavior -v
Confirm:
Test passes immediately? You're testing existing behavior. Fix the test.
Test errors? Fix the error, re-run until it fails correctly.
Write the simplest code to pass the test. Nothing more.
Good:
def add(a, b):
return a + b # Nothing extra
Bad:
def add(a, b):
result = a + b
logging.info(f"Adding {a} + {b} = {result}") # Extra!
return result
Don't add features, refactor other code, or "improve" beyond the test.
Cheating is OK in GREEN:
We'll fix it in REFACTOR.
MANDATORY.
# Run the specific test
pytest tests/test_feature.py::test_specific_behavior -v
# Then run ALL tests to check for regressions
pytest tests/ -q
Confirm:
Test fails? Fix the code, not the test.
Other tests fail? Fix regressions now.
After green only:
Keep tests green throughout. Don't add behavior.
If tests fail during refactor: Undo immediately. Take smaller steps.
Next failing test for next behavior. One cycle at a time.
"I'll write tests after to verify it works"
Tests written after code pass immediately. Passing immediately proves nothing:
Test-first forces you to see the test fail, proving it actually tests something.
"I already manually tested all the edge cases"
Manual testing is ad-hoc. You think you tested everything but:
Automated tests are systematic. They run the same way every time.
"Deleting X hours of work is wasteful"
Sunk cost fallacy. The time is already gone. Your choice now:
The "waste" is keeping code you can't trust.
"TDD is dogmatic, being pragmatic means adapting"
TDD IS pragmatic:
"Pragmatic" shortcuts = debugging in production = slower.
"Tests after achieve the same goals — it's spirit not ritual"
No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"
Tests-after are biased by your implementation. You test what you built, not what's required. Tests-first force edge case discovery before implementing.
| Excuse | Reality |
|---|---|
| "Too simple to test" | Simple code breaks. Test takes 30 seconds. |
| "I'll test after" | Tests passing immediately prove nothing. |
| "Tests after achieve same goals" | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested" | Ad-hoc ≠ systematic. No record, can't re-run. |
| "Deleting X hours is wasteful" | Sunk cost fallacy. Keeping unverified code is technical debt. |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete. |
| "Need to explore first" | Fine. Throw away exploration, start with TDD. |
| "Test hard = design unclear" | Listen to the test. Hard to test = hard to use. |
| "TDD will slow me down" | TDD faster than debugging. Pragmatic = test-first. |
| "Manual test faster" | Manual doesn't prove edge cases. You'll re-test every change. |
| "Existing code has no tests" | You're improving it. Add tests for the code you touch. |
If you catch yourself doing any of these, delete the code and restart with TDD:
All of these mean: Delete code. Start over with TDD.
Before marking work complete:
Can't check all boxes? You skipped TDD. Start over.
| Problem | Solution |
|---|---|
| Don't know how to test | Write the wished-for API. Write the assertion first. Ask the user. |
| Test too complicated | Design too complicated. Simplify the interface. |
| Must mock everything | Code too coupled. Use dependency injection. |
| Test setup huge | Extract helpers. Still complex? Simplify the design. |
Use the terminal tool to run tests at each step:
# RED — verify failure
terminal("pytest tests/test_feature.py::test_name -v")
# GREEN — verify pass
terminal("pytest tests/test_feature.py::test_name -v")
# Full suite — verify no regressions
terminal("pytest tests/ -q")
When dispatching subagents for implementation, enforce TDD in the goal:
delegate_task(
goal="Implement [feature] using strict TDD",
context="""
Follow test-driven-development skill:
1. Write failing test FIRST
2. Run test to verify it fails
3. Write minimal code to pass
4. Run test to verify it passes
5. Refactor if needed
6. Commit
Project test command: pytest tests/ -q
Project structure: [describe relevant files]
""",
toolsets=['terminal', 'file']
)
Bug found? Write failing test reproducing it. Follow TDD cycle. The test proves the fix and prevents regression.
Never fix bugs without a test.
When testing functions that depend on module-level state (caches, singletons, _db = None, _session_id = None), you must reset those globals both before AND after each test — otherwise stale state from one test leaks into the next, causing order-dependent failures that vanish when run in isolation.
Wrong (only reset before):
def test_session_id_reads_file():
hooks_module._session_id = None # reset before
result = hooks_module._get_session_id()
assert result == "expected"
# _session_id is now cached — next test sees stale value!
Right (reset both before and after, or use a fixture):
@pytest.fixture()
def fresh_hooks(tmp_path):
mock_db = MagicMock(spec=AetherDBSync)
hooks_module._aether_db = None
hooks_module._session_id = None
hooks_module._agent_name = None
yield {"db": mock_db, "tmp_path": tmp_path}
# Cleanup — even if test failed, next test gets clean state
hooks_module._aether_db = None
hooks_module._session_id = None
hooks_module._agent_name = None
Why both directions? Reset-before ensures the test starts clean. Reset-after (in fixture teardown) ensures a failing test doesn't poison the next one. Without teardown, pytest -x stops at the first failure but pytest (full suite) cascades false failures.
Pattern: direct attribute set on the module. Use hooks_module._session_id = None (direct attribute assignment), not import hooks; hooks._session_id = None from a different import path — Python module identity depends on import path, and test files may import via a different path than the module's own internal references.
Pattern: PID-suffixed files for concurrent isolation. When multiple processes share a directory (e.g., HERMES_HOME), use {filename}.{os.getpid()} for files that identify the current process. In tests, write files using os.getpid() to match what the production code reads, not hardcoded PIDs. The PID file takes priority over the generic (non-PID) file, with fallback to the generic file for single-process or backward-compatible mode.
Production code → test exists and failed first
Otherwise → not TDD
No exceptions without the user's explicit permission.