Skip to content
Back to skills

Tdd

ASecurity

Enforce test-driven development — write tests FIRST, then implement. Mandatory for new features and bug fixes.

  • 59 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 22, 2026
ai-agentspythongobashsqltestingrefactoringapidatabase

Works with

  • cli
  • api

Security analysis

A93/100
  • highPerforms destructive filesystem operations

Pro shows the line behind each finding and how to fix it

Scanned September 22, 2026

npx -y skills add ebibibi/ebi-agent-chat-relay --skill tdd --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Tdd?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Tdd
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ebibibi-tdd/badge)](https://www.skillsdirectory.com/skills/ebibibi-tdd)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: tdd
description: Enforce test-driven development — write tests FIRST, then implement. Mandatory for new features and bug fixes.
---

# TDD — Test-Driven Development (Enforced)

**This is not a suggestion. This is the workflow.**

When adding features or fixing bugs in claude-code-discord-bridge, you MUST write tests before writing implementation code. No exceptions.

## When to Activate

- Adding any new feature or functionality
- Fixing a bug (write a test that reproduces the bug FIRST)
- Refactoring code (ensure existing tests pass, add missing ones)
- Adding a new Cog, parser rule, UI component, or database operation

## The Cycle: RED → GREEN → REFACTOR

### Step 1: RED — Write Failing Tests

Write tests that describe the desired behavior. Run them. **They must fail.**

```bash
uv run pytest tests/test_new_feature.py -v
# Expected: FAILED (because the code doesn't exist yet)
```

If tests pass before you write any implementation, your tests are wrong.

### Step 2: GREEN — Write Minimal Implementation

Write the **minimum code** to make the tests pass. No more.

```bash
uv run pytest tests/test_new_feature.py -v
# Expected: PASSED
```

### Step 3: REFACTOR — Clean Up

Improve the implementation while keeping all tests green. Then run the full suite:

```bash
uv run pytest tests/ -v --cov=claude_discord --cov-report=term-missing
```

### Step 4: VERIFY — Lint + Format + Full Suite

```bash
uv run ruff check claude_discord/
uv run ruff format claude_discord/
uv run pytest tests/ -v --cov=claude_discord
```

## What to Test First (by module type)

### New Parser Rule

```python
# tests/test_parser.py — Write THIS first
def test_parse_new_event_type():
    line = '{"type":"new_type","data":"value"}'
    event = parse_line(line)
    assert event is not None
    assert event.message_type == MessageType.ASSISTANT

def test_parse_new_event_type_missing_data():
    line = '{"type":"new_type"}'
    event = parse_line(line)
    assert event is not None  # Should handle gracefully

# THEN implement in claude_discord/claude/parser.py
```

### New Cog Command

```python
# tests/test_new_cog.py — Write THIS first
from unittest.mock import AsyncMock, MagicMock

def test_rejects_unauthorized_user():
    cog = MyCog(bot=MagicMock(), allowed_user_ids={111})
    assert cog._is_authorized(999) is False

def test_accepts_authorized_user():
    cog = MyCog(bot=MagicMock(), allowed_user_ids={111})
    assert cog._is_authorized(111) is True

def test_validates_input_format():
    # Test that invalid input is rejected before reaching CLI
    assert not re.match(r"^[\w-]+$", "invalid; rm -rf /")

# THEN implement in claude_discord/cogs/my_cog.py
```

### New Chunker Behavior

```python
# tests/test_chunker.py — Write THIS first
def test_chunk_with_nested_code_blocks():
    text = "```python\n```nested```\n```"
    chunks = chunk_message(text)
    # Verify code blocks are not broken
    for chunk in chunks:
        assert chunk.count("```") % 2 == 0  # Even number = balanced

def test_chunk_unicode_content():
    text = "日本語テスト 🎉" * 500
    chunks = chunk_message(text)
    assert all(len(c.encode("utf-8")) <= 8000 for c in chunks)

# THEN implement the fix in claude_discord/discord_ui/chunker.py
```

### Bug Fix

```python
# tests/test_bug_123.py — Write THIS first
def test_session_id_with_uppercase_rejected():
    """Bug #123: Session IDs with uppercase hex were accepted."""
    runner = ClaudeRunner()
    with pytest.raises(ValueError):
        runner._build_args("hello", session_id="ABC-DEF")

# Confirm the test FAILS with current code
# THEN fix the regex in runner.py
# THEN confirm the test PASSES
```

### Database Operation

```python
# tests/test_repository.py — Write THIS first
async def test_save_overwrites_existing(repo):
    await repo.save(thread_id=123, session_id="old-id")
    await repo.save(thread_id=123, session_id="new-id")
    result = await repo.get_session_id(thread_id=123)
    assert result == "new-id"

async def test_concurrent_saves(repo):
    import asyncio
    tasks = [
        repo.save(thread_id=i, session_id=f"session-{i}")
        for i in range(100)
    ]
    await asyncio.gather(*tasks)
    # All should succeed without errors

# THEN implement or verify in claude_discord/database/repository.py
```

## Coverage Requirements

| Module | Target | Rationale |
|--------|--------|-----------|
| parser.py | 90%+ | Pure logic, no dependencies, easy to test |
| chunker.py | 90%+ | Pure logic, edge cases are critical |
| types.py | 90%+ | Data structures and enums |
| repository.py | 90%+ | Real SQLite, no mocking needed |
| runner.py | 60%+ | Test args/env building; subprocess is harder |
| Cogs | 30%+ | Discord mocking is heavy; test validation logic |
| status.py | 30%+ | Async + Discord API mocking |
| **Overall** | **50%+** | **And always increasing** |

## Anti-Patterns (Don't Do This)

### Writing implementation first, tests later
```
❌ "I'll write the code first and add tests after"
✅ "I'll write the test first, watch it fail, then implement"
```

### Testing implementation details
```python
# ❌ Bad: Testing internal state
assert runner._process is not None

# ✅ Good: Testing observable behavior
args = runner._build_args("hello", None)
assert args[-1] == "hello"
```

### Skipping the RED step
```
❌ Writing a test that passes immediately
✅ Writing a test, running it, confirming it FAILS, then implementing
```

### Over-mocking
```python
# ❌ Bad: Mocking everything
mock_everything = MagicMock()
result = function_under_test(mock_everything)
# What are you even testing?

# ✅ Good: Mock only external boundaries
real_runner = ClaudeRunner(command="claude")
args = real_runner._build_args("test prompt", None)
assert "--" in args
```

## Quick Reference

```bash
# Run specific test file
uv run pytest tests/test_parser.py -v

# Run specific test
uv run pytest tests/test_parser.py::test_parse_system_message -v

# Run with coverage
uv run pytest tests/ -v --cov=claude_discord --cov-report=term-missing

# Stop on first failure
uv run pytest tests/ -x

# Show local variables on failure
uv run pytest tests/ -v --tb=short -l
```

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.