Module 8: Capstone Project — A Complete Test Suite with TDD

Spec-First Planning: Designing from the Tests

Spec-First Planning: Designing from the Tests

Capsule overview

Before writing a single line of implementation, you're going to design the TaskFlow API from the tests. This capsule is pure planning: decomposing features into user stories, mapping each story to testable specs, defining the exact names of the tests you'll write, and creating the empty file structure.

You don't implement anything here. You design the contract between what the application must do and how you'll validate it. By the end, you'll have a complete blueprint that guides all the subsequent development.


Why Planning Before Code?

The problem with "just start coding"

When you start implementing without planning, you tend to:

  • Write features that don't cover all the necessary cases
  • Discover requirements halfway through
  • End up with tests that don't reflect the expected behavior
  • Waste time refactoring because the architecture didn't anticipate certain cases

The spec-first approach

In spec-first, you define the tests as a specification before implementing:

  • The tests are the contract: "the app must do X, Y, Z"
  • Claude Code implements against that contract
  • There's no ambiguity: the test passes or fails
  • Feature decomposition forces you to think about edge cases before writing code

Feature Decomposition: Breaking Features into Specs

What feature decomposition is

Feature decomposition is splitting a large feature ("authentication") into individual, testable specs. Each spec is a test with a name that describes exactly what must happen.

An example:

A large feature: "Authentication"
  → Spec 1: register with a valid email creates a user
  → Spec 2: register with a duplicate email fails
  → Spec 3: login with correct credentials returns a token
  → Spec 4: login with an incorrect password fails
  ...

How to decompose

For each feature, ask yourself:

  1. What's the happy path?
  2. What errors can occur?
  3. What edge cases exist?
  4. What validations must apply?

Each answer becomes the name of a test.


User Stories → Test Specs: The Mapping

Feature 1: Authentication

User stories:

  • As a user, I want to register with an email and password so I have an account
  • As a user, I want to log in with my credentials to get a token
  • As a user, I want my token to be valid so I can access protected endpoints
  • As a user, I want invalid credentials to be rejected

Test specs (8-10 tests):

#Test nameWhat it validates
1test_register_creates_user_with_valid_email_and_passwordA successful registration creates a user and returns the data (without the password)
2test_register_duplicate_email_failsAn already-registered email → ValueError
3test_register_invalid_email_raisesA malformed email → ValueError
4test_register_weak_password_raisesA password that doesn't meet the requirements → ValueError
5test_login_returns_token_for_valid_credentialsA successful login returns a token string
6test_login_wrong_password_raisesAn incorrect password → ValueError
7test_login_nonexistent_user_raisesThe user doesn't exist → ValueError
8test_validate_token_returns_user_for_valid_tokenA valid token returns the user's data
9test_validate_token_raises_for_invalid_tokenAn invalid or expired token → ValueError
10test_password_is_hashed_on_registerThe password is never stored in plain text

Feature 2: Teams

User stories:

  • As a user, I want to create a team to organize tasks with others
  • As an owner, I want to add members to my team
  • As a user, I want to list the teams I belong to

Test specs (6-8 tests):

#Test nameWhat it validates
1test_create_team_returns_team_with_ownerCreating a team returns the team with the creator as owner
2test_create_team_requires_authenticated_userWithout a token → 401
3test_add_member_adds_user_to_teamAdding a member successfully
4test_add_member_requires_owner_permissionOnly the owner can add members
5test_add_member_duplicate_raisesAdding an already-existing member → error
6test_list_teams_returns_user_teamsListing teams returns only the user's
7test_list_team_members_returns_correct_usersListing members returns the owner + members
8test_get_team_by_id_returns_team_or_404Getting a team by ID, 404 if it doesn't exist

Feature 3: Tasks

User stories:

  • As a user, I want to create tasks within a team
  • As a user, I want to assign tasks to team members
  • As a user, I want to change tasks' status (pending → in progress → completed)
  • As a user, I want to filter tasks by status, priority and assignee

Test specs (10-12 tests):

#Test nameWhat it validates
1test_create_task_returns_task_in_teamCreating a task returns the task with the correct data
2test_create_task_requires_team_membershipA non-member user can't create a task
3test_get_task_by_id_returns_task_or_404Getting a task by ID
4test_update_task_modifies_fieldsUpdating the title, description, etc.
5test_delete_task_removes_from_teamDeleting a task removes it from the team
6test_assign_task_to_team_member_succeedsAssigning a task to a team member
7test_assign_task_to_non_member_failsAssigning to a non-member fails
8test_transition_pending_to_in_progress_succeedsA valid transition pending → in_progress
9test_transition_in_progress_to_completed_succeedsA valid transition in_progress → completed
10test_filter_tasks_by_status_returns_matchingFiltering by status returns only the matching ones
11test_filter_tasks_by_priority_returns_matchingFiltering by priority
12test_filter_tasks_by_assignee_returns_matchingFiltering by assignee

Feature 4: Business Logic

User stories:

  • As the system, I must validate that status transitions are valid (no going backwards)
  • As the system, I must validate permissions: only the assignee can complete a task
  • As a user, I want to see the team's statistics (tasks by status, completion rate)

Test specs (6-8 tests):

#Test nameWhat it validates
1test_transition_completed_to_in_progress_raisesYou can't go backwards completed → in_progress
2test_transition_pending_to_completed_raisesYou can't skip pending → completed (it must go through in_progress)
3test_only_assigned_user_can_complete_taskOnly the assignee can mark it as completed
4test_team_owner_can_add_membersOwner permission to add
5test_get_team_stats_returns_tasks_by_statusThe stats return the count per status
6test_get_team_stats_completion_rateThe completion rate is calculated correctly
7test_unassigned_task_can_be_completed_by_any_memberAn unassigned task: any member can complete it
8test_invalid_status_transition_raisesA transition to an invalid status → ValueError

Creating the Test Structure (Empty Files)

The directory structure

taskflow/
├── app/
│   └── ... (the app's structure)
├── tests/
│   ├── conftest.py
│   ├── unit/
│   │   ├── conftest.py
│   │   ├── test_auth.py
│   │   ├── test_teams.py
│   │   ├── test_tasks.py
│   │   └── test_rules.py
│   ├── integration/
│   │   ├── conftest.py
│   │   ├── test_auth_endpoints.py
│   │   ├── test_team_endpoints.py
│   │   └── test_task_endpoints.py
│   └── e2e/
│       ├── conftest.py
│       └── test_flows.py

File: tests/unit/test_auth.py (the empty structure)

# tests/unit/test_auth.py
# Tests for the auth service (registration, login, token validation)

def test_register_creates_user_with_valid_email_and_password():
    pass

def test_register_duplicate_email_fails():
    pass

def test_register_invalid_email_raises():
    pass

def test_register_weak_password_raises():
    pass

def test_login_returns_token_for_valid_credentials():
    pass

def test_login_wrong_password_raises():
    pass

def test_login_nonexistent_user_raises():
    pass

def test_validate_token_returns_user_for_valid_token():
    pass

def test_validate_token_raises_for_invalid_token():
    pass

def test_password_is_hashed_on_register():
    pass

File: tests/unit/test_teams.py

# tests/unit/test_teams.py

def test_create_team_returns_team_with_owner():
    pass

def test_add_member_adds_user_to_team():
    pass

def test_add_member_requires_owner_permission():
    pass

def test_add_member_duplicate_raises():
    pass

def test_list_teams_returns_user_teams():
    pass

def test_list_team_members_returns_correct_users():
    pass

def test_get_team_by_id_returns_team_or_404():
    pass

File: tests/unit/test_tasks.py

# tests/unit/test_tasks.py

def test_create_task_returns_task_in_team():
    pass

def test_create_task_requires_team_membership():
    pass

def test_get_task_by_id_returns_task_or_404():
    pass

def test_update_task_modifies_fields():
    pass

def test_delete_task_removes_from_team():
    pass

def test_assign_task_to_team_member_succeeds():
    pass

def test_assign_task_to_non_member_fails():
    pass

def test_filter_tasks_by_status_returns_matching():
    pass

def test_filter_tasks_by_priority_returns_matching():
    pass

def test_filter_tasks_by_assignee_returns_matching():
    pass

File: tests/unit/test_rules.py

# tests/unit/test_rules.py
# Business logic: status transitions, permissions, stats

def test_transition_completed_to_in_progress_raises():
    pass

def test_transition_pending_to_completed_raises():
    pass

def test_only_assigned_user_can_complete_task():
    pass

def test_get_team_stats_returns_tasks_by_status():
    pass

def test_get_team_stats_completion_rate():
    pass

def test_unassigned_task_can_be_completed_by_any_member():
    pass

def test_invalid_status_transition_raises():
    pass

The integration files (the structure)

For integration, the tests validate the endpoints with TestClient. The names reflect the HTTP behavior:

  • test_register_endpoint_returns_201_and_user
  • test_login_endpoint_returns_200_and_token
  • test_protected_endpoint_returns_401_without_token
  • test_create_team_endpoint_returns_201
  • test_add_member_endpoint_returns_200
  • test_create_task_endpoint_returns_201
  • etc.

(In the next capsule you'll implement these with real code.)


Prioritization: Core First, Business Logic Later

The recommended order

You don't implement all the tests in parallel. You follow an order that minimizes dependencies:

Phase 1 — Core (first):

  • Auth (the whole module): without auth there are no users, without users there are no teams
  • Teams (create, list): without teams there are no tasks
  • Tasks (basic CRUD): create, read, update, delete

Phase 2 — Business logic:

  • Status transitions
  • Permissions (who can do what)
  • Stats and filters
  • Edge cases

Why this order

  • Auth is the foundation: every protected endpoint depends on the token
  • Teams is the second layer: tasks belong to teams
  • Tasks CRUD is the functional core: without it the app doesn't do anything useful
  • Business logic refines the behavior but presupposes that the CRUD exists

The Planning Checklist

Before moving on to the implementation, verify that you have:

  • A complete list of tests per feature (auth, teams, tasks, business logic)
  • Test files created with pass and descriptive names
  • The implementation order defined (auth → teams → tasks → rules)
  • Clarity about which tests are unit vs integration vs E2E

Summary

  • Feature decomposition turns "I want auth" into 10 concrete specs
  • Each user story maps to tests with names that document the behavior
  • You create the empty file structure before implementing
  • You prioritize: core features (auth, teams, tasks CRUD) first; business logic later
  • This capsule is only planning — in the next one the real TDD begins

Next capsule: Core Features with TDD — implementing auth, teams and tasks cycle by cycle.


Module 8, Capsule 02 — Testing with Claude Code Guide