Skill Library

intermediate Code Development

TDD Development Workflow

Test-Driven Development workflow for AI agents. Write tests first, implement to pass, then refactor. Includes patterns for red-green-refactor cycle, mocking, and comprehensive test coverage.

When to Use This Skill

  • Starting new feature development
  • Fixing bugs (write a failing test first)
  • Refactoring existing code safely
  • When high confidence in code correctness is needed
  • Building APIs or libraries that need stable contracts

How to use this skill

1. Copy the AI Core Logic from the Instructions tab below.

2. Paste it into your AI's System Instructions or as your first message.

3. Provide your raw data or requirements as requested by the AI.

#tdd#testing#workflow#best-practices#unit-testing#refactoring

System Directives

## The Red-Green-Refactor Cycle ``` TDD follows a strict cycle: 1. 🔴 RED: Write a failing test - Test doesn't exist yet - Run test → it should FAIL 2. 🟢 GREEN: Write minimal code to pass - Implement just enough to make test pass - Don't over-engineer - Run test → it should PASS 3. 🔄 REFACTOR: Improve the code - Clean up implementation - Remove duplication - Improve readability - Run tests → they should still PASS ``` ## TDD Workflow in Practice ### Step 1: Understand the Requirement ``` Before writing any code: 1. Clarify what needs to be built 2. Break into small, testable behaviors 3. Identify edge cases 4. Determine expected inputs/outputs Example requirement: "Create a function that validates email addresses" Behaviors to test: - Valid email returns true - Invalid email returns false - Empty string returns false - Email without @ returns false - Email without domain returns false ``` ### Step 2: Write the First Failing Test ```python import pytest from email_validator import is_valid_email def test_valid_email_returns_true(): """A properly formatted email should be valid.""" assert is_valid_email("user@example.com") == True ``` ```javascript // email-validator.test.js const { isValidEmail } = require('./email-validator'); describe('isValidEmail', () => { test('valid email returns true', () => { expect(isValidEmail('user@example.com')).toBe(true); }); }); ``` **Run the test → It should FAIL (function doesn't exist)** ### Step 3: Write Minimal Implementation ```python def is_valid_email(email: str) -> bool: return "@" in email and "." in email ``` ```javascript // email-validator.js function isValidEmail(email) { return email.includes('@') && email.includes('.'); } module.exports = { isValidEmail }; ``` **Run the test → It should PASS** ### Step 4: Add More Tests ```python def test_email_without_at_returns_false(): """Email missing @ symbol is invalid.""" assert is_valid_email("userexample.com") == False def test_empty_string_returns_false(): """Empty string is invalid.""" assert is_valid_email("") == False def test_email_without_domain_returns_false(): """Email without domain is invalid.""" assert is_valid_email("user@") == False def test_email_with_multiple_at_returns_false(): """Email with multiple @ is invalid.""" assert is_valid_email("user@@example.com") == False ``` ### Step 5: Refactor to Pass All Tests ```python import re def is_valid_email(email: str) -> bool: """ Validate email address format. Args: email: The email address to validate Returns: True if email is valid, False otherwise """ if not email or not isinstance(email, str): return False pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return bool(re.match(pattern, email)) ``` ## TDD Patterns ### Arrange-Act-Assert (AAA) ```python def test_user_creation(): user_data = {"name": "Alice", "email": "alice@example.com"} user = User.create(user_data) assert user.name == "Alice" assert user.email == "alice@example.com" assert user.id is not None ``` ### Given-When-Then (BDD Style) ```python def test_user_can_update_profile(): user = create_test_user(name="Alice") user.update_profile(name="Alicia") assert user.name == "Alicia" ``` ### Test Isolation with Mocking ```python from unittest.mock import Mock, patch def test_sends_email_on_registration(): """Verify email is sent when user registers.""" with patch('services.email.send') as mock_send: register_user("alice@example.com") mock_send.assert_called_once_with( to="alice@example.com", subject="Welcome!", body=mock.ANY ) ``` ### Parameterized Tests ```python import pytest @pytest.mark.parametrize("email,expected", [ ("user@example.com", True), ("user.name@example.co.uk", True), ("invalid", False), ("@example.com", False), ("user@", False), ("", False), (None, False), ]) def test_email_validation(email, expected): assert is_valid_email(email) == expected ``` ## Testing Strategies by Code Type ### Pure Functions ```python def test_calculate_total(): items = [{"price": 10}, {"price": 20}] assert calculate_total(items) == 30 ``` ### Classes with State ```python def test_shopping_cart_lifecycle(): cart = ShoppingCart() assert cart.total == 0 cart.add_item(Item(price=10)) cart.add_item(Item(price=20)) assert cart.total == 30 cart.remove_item(0) assert cart.total == 20 ``` ### External Dependencies ```python def test_fetches_user_from_api(): """Test API integration with mock.""" with patch('requests.get') as mock_get: mock_get.return_value.json.return_value = { "id": 1, "name": "Alice" } mock_get.return_value.status_code = 200 user = fetch_user(1) assert user["name"] == "Alice" mock_get.assert_called_with("/api/users/1") ``` ### Async Code ```python import pytest @pytest.mark.asyncio async def test_async_data_fetch(): result = await fetch_data_async() assert result is not None assert "data" in result ``` ## Common TDD Mistakes to Avoid ``` ❌ Writing too many tests at once → Write ONE test, make it pass, repeat ❌ Writing implementation before tests → Always test first, even if you know the answer ❌ Making tests pass with hardcoded values → Use real logic, refactor after ❌ Testing implementation details → Test behavior, not internal structure ❌ Skipping the refactor step → Always clean up after going green ❌ Not running tests frequently → Run after every small change ``` ## TDD Command Cheatsheet ```bash pytest # Run all tests pytest -v # Verbose output pytest -x # Stop on first failure pytest --tb=short # Short tracebacks pytest -k "test_email" # Run matching tests pytest --cov=src # With coverage npm test # Run all tests npm test -- --watch # Watch mode npm test -- --coverage # With coverage npm test -- -t "email" # Run matching tests go test ./... # Run all tests go test -v # Verbose go test -cover # With coverage go test -run TestEmail # Run matching tests ``` ## Best Practices 1. **One Assertion Per Test** (when practical): Tests should verify one specific behavior 2. **Descriptive Test Names**: `test_user_with_invalid_email_is_rejected` 3. **Independent Tests**: Tests shouldn't depend on order or shared state 4. **Fast Tests**: Unit tests should run in milliseconds 5. **Meaningful Failures**: Error messages should indicate what went wrong 6. **Test Edge Cases**: Empty inputs, nulls, boundaries, error conditions ## Related Resources - [Test-Driven Development by Kent Beck](https://www.oreilly.com/library/view/test-driven-development/0321146530/) - [pytest Documentation](https://docs.pytest.org/) - [Jest Documentation](https://jestjs.io/) - [The Art of Unit Testing](https://www.manning.com/books/the-art-of-unit-testing-third-edition)

Procedural Integration

This skill is formatted as a set of persistent system instructions. When integrated, it provides the AI model with specialized workflows and knowledge constraints for Code Development.

Skill Actions


Model Compatibility
🤖 Claude Opus🤖 Claude 3.5 Sonnet🤖 GPT-4
Code Execution: Required
MCP Tools: Optional
Footprint ~2,072 tokens