Claude Code Testing Guide: TDD, Mocking & Coverage

📘 AI Tutorials 💬 🔥 Trending

🩺 Summary

Most developers know they should write tests. They just don'...

📝 Details

Most developers know they should write tests. They just don't want to. Not because testing is useless—because writing tests manually is tedious. Mock data, assertions, coverage chasing—repetitive and mentally draining. Claude Code's most underrated skill isn't writing production code, it's **writing tests**. This guide skips the theory and goes straight to practice: TDD with Claude, auto-mocking, coverage optimization.
## 1. TDD Mode: Test First, Code Second TDD: write a failing test first, then write the code to make it pass. Claude Code is naturally good at this because you don't need to switch context. ### 1.1 Ask Claude to Write Tests First For an empty `calculator.py`: ```bash claude "TDD this calculator module. Write pytest tests first, then implement. Requirements: - add(a, b) returns sum - subtract(a, b) returns a - b - multiply(a, b) returns product - divide(a, b) returns quotient, ZeroDivisionError for 0 - Cover normal and edge cases" ``` Claude creates `test_calculator.py` first, then `calculator.py`. All tests pass. **Comparison:** | Step | Manual TDD | Claude Code TDD | |:----|:-----------|:----------------| | Write tests | 10 min | 30s (auto) | | Implement | 15 min | 20s (auto) | | Run tests | 5s | 5s | | Refactor | 10 min | 30s (auto) | | **Total** | **~35 min** | **~1.5 min** | ### 1.2 Backfill Tests for Existing Code More common scenario: project is half-built, needs tests now. ```bash claude -p "Write pytest tests for @models/user.py: - Cover all public methods - Mock database (no real DB) - Normal and error flows - Target 90%+ coverage" ``` Claude reads `models/user.py`, analyzes function signatures and branches, and generates the test file. **Pro tip**: Reference files with `@`: ```bash claude -p "Write unit tests for @models/user.py using pytest + unittest.mock" ``` ## 2. Mocking: Don't Touch Real Dependencies The hardest part of testing: the function calls a database, sends HTTP requests, or accesses the filesystem. Mocking replaces these with stand-ins. ### 2.1 Let Claude Auto-Generate Mocks ```bash claude -p "Write tests for @services/payment.py: - Mock PaymentGateway.send() - Mock EmailService.notify() - Verify welcome email on success - Verify retry logic on failure - Use pytest monkeypatch or unittest.mock" ``` Claude analyzes the service interfaces and generates correct mock code automatically. ### 2.2 Common Mock Templates **HTTP Mock:** ```python from unittest.mock import patch, MagicMock def test_fetch_user_data(): mock_response = {"id": 1, "name": "Test", "email": "test@example.com"} with patch("services.user.requests.get") as mock_get: mock_get.return_value.status_code = 200 mock_get.return_value.json.return_value = mock_response result = fetch_user_data(1) assert result["name"] == "Test" ``` **Database Mock:** ```python @pytest.fixture def mock_db(): with patch("models.user.get_db_connection") as mock_conn: cursor = MagicMock() mock_conn.cursor.return_value = cursor cursor.fetchone.return_value = {"id": 1, "name": "Alice"} yield mock_conn ``` **Filesystem Mock:** ```python from unittest.mock import mock_open, patch def test_process_csv(): with patch("builtins.open", mock_open(read_data="name,age\nAlice,30")): result = process_csv("data.csv") assert len(result) == 1 ``` ### 2.3 Mock Best Practices Add these to your CLAUDE.md: ```markdown ## Testing Conventions - Use pytest + unittest.mock - Mock external dependencies (DB, HTTP, filesystem) - One test, one concern - Name: test_<feature>_<scenario> - Shared fixtures in conftest.py ``` ## 3. Coverage Optimization ### 3.1 Scan Coverage Gaps ```bash pytest --cov=./src --cov-report=json claude "Analyze coverage.json. Find modules below 80%. Suggest missing test scenarios for each." ``` ### 3.2 Fill Gaps for Low-Coverage Files ```bash claude -p "Read @src/utils/validator.py coverage report. Add tests for uncovered branches: - Each if/else path - Exception handlers - Edge cases (empty, None, extreme values)" ``` ### 3.3 Phased Coverage Strategy ```bash # Phase 1: Core logic → 80% claude -p "Write tests for @src/core/*, target 80% coverage" # Phase 2: Critical paths → 85% claude -p "Write tests for @src/api/* handlers, target 85%" # Phase 3: Config → 70% claude -p "Write tests for @src/config/* parsers, target 70%" ``` ## 4. Real Project Example A `register.py` user registration module: ```python import re import requests class UserRegistration: def __init__(self, db_conn, email_service): self.db = db_conn self.email = email_service def register(self, username, email, password): if not re.match(r'^[a-zA-Z0-9_]{3,20}$', username): raise ValueError("Invalid username") if not re.match(r'^[^@]+@[^@]+\.[^@]+$', email): raise ValueError("Invalid email") if len(password) < 8: raise ValueError("Password too short") if self.db.query("SELECT id FROM users WHERE email=?", (email,)): raise ValueError("Email already registered") user_id = self.db.execute("INSERT INTO users ...", (username, email, password)) self.email.send_welcome(email, username) return {"id": user_id, "username": username, "email": email} ``` ```bash claude -p "Write complete pytest tests for @register.py: - Mock db and email_service - Cover: success, invalid username, invalid email, short password, duplicate email - Verify email.send_welcome called on success, not called on failure - Hit 100% branch coverage" ``` Claude generates: ```python @pytest.fixture def registration(): db = MagicMock() email = MagicMock() return UserRegistration(db, email), db, email def test_success(registration): reg, db, email = registration db.query.return_value = None db.execute.return_value = 42 result = reg.register("testuser", "test@example.com", "password123") assert result["id"] == 42 email.send_welcome.assert_called_once() def test_invalid_username(registration): reg, _, _ = registration with pytest.raises(ValueError, match="Invalid username"): reg.register("ab", "test@example.com", "password123") ``` ## 5. Test Maintenance ### 5.1 Update Tests When Code Changes ```bash # After changing code: claude -p "I changed @services/payment.py process_refund signature from (amount) to (amount, reason). Update the tests." ``` ### 5.2 Bulk Test Updates ```bash claude -p "Check all test files in @tests/ for collection errors due to API changes. Update based on new @src/ signatures." ``` ### 5.3 Review Tests Themselves ```bash claude -p "Review @tests/test_payment.py: - Missing scenarios? - Mocks correct (args, return types)? - Assertions strict enough? - Unnecessary mocks?" ``` --- **Quick Reference:** | Scenario | Command | Result | |:---------|:--------|:-------| | TDD new feature | `claude "TDD module.py"` | Tests + implementation | | Backfill tests | `claude "Write tests for @file.py"` | Auto-generated tests | | Mock deps | `claude "Mock DB and HTTP"` | Auto-mocked externals | | Cover gaps | `claude "Analyze coverage, fill gaps"` | Targeted improvements | | Update tests | `claude "Signature changed, update tests"` | Auto-adapted tests | The biggest barrier to testing isn't technical—it's inertia. Claude Code reduces that barrier to zero. Just say "write tests" and it's done. --- *More from the series:* - [Hands-on: Build a Web App in 10 Minutes](/post/claude-code-todo-project-2026) - [Advanced: CLAUDE.md & MCP Config](/post/claude-code-advanced-2026) - [Pitfalls: 10 Common Mistakes](/post/claude-code-pitfalls-2026) - [2026 AI Coding Tools Guide](/post/ai-coding-tools-guide-2026) - [Team Collaboration Guide](/post/claude-code-team-collaboration-2026) - [CI/CD Integration](/post/claude-code-ci-cd-2026) - [Can Claude Code Be Used in Hong Kong?](/post/claude-code-xianggang-2026)