MeshiTrack/docs/tdd-transition-plan.md

182 lines
9.8 KiB
Markdown
Raw Normal View History

2026-05-19 11:06:03 +09:00
# TDD Transition Plan — MeshiTrack
This document details the blueprint for transitioning the MeshiTrack monorepo to a strict Test-Driven Development (TDD) workflow. It outlines the specific steps, tooling enhancements, repository guidelines, and architectural patterns required to shift from "test-after" to "test-first" development.
---
## 1. Objectives
1. **Maintain Strict Quality Bars**: Ensure all new code seamlessly meets our high coverage gates (100% lines/functions/statements, 90% branches for `packages/api` & `packages/shared`, and 90% lines, 85% functions, 75% branches, 85% statements for `packages/web`).
2. **Prevent Architecture Regression**: Stop implementing business logic before definitions and type interfaces are established in `packages/shared`.
3. **Elevate DX and Iteration Speed**: Speed up the local feedback loop (Vitest/RTL runtimes) to enable a continuous Red-Green-Refactor cycle.
4. **Standardize Mocks and Factories**: Establish reusable test builders and automated dependency mocks to reduce boilerplate.
---
## 2. The MeshiTrack TDD Loop (Red-Green-Refactor)
TDD requires writing tests *before* writing the corresponding implementation. In MeshiTrack, this cycle is applied iteratively across each vertical slice.
```mermaid
graph TD
A[1. Write Failing Test - RED] --> B[2. Run Test & Verify Failure]
B --> C[3. Write Minimal Code - GREEN]
C --> D[4. Run Test & Verify Pass]
D --> E[5. Refactor Code & Clean Boilerplate]
E --> F[6. Run All Tests to Prevent Regression]
F --> A
```
### The Rules of TDD in MeshiTrack:
1. **Write no production code** unless it is to make a failing unit or integration test pass.
2. **Write only enough of a test** to demonstrate a failure (compilation failure counts as a failure).
3. **Write only enough production code** to make the single failing test pass.
4. **Refactor immediately** after going Green, while both the unit test and existing regression suite are passing.
---
## 3. Layer-by-Layer TDD Guide
### Layer 1: Shared Package (`packages/shared`)
The shared package contains domain types, enums, and Zod schemas. It must remain pure TypeScript.
1. **Red**: Define the interface or enum signature in TypeScript. Write a test in `src/validation/*.test.ts` asserting how the validation schema should handle valid, invalid, and edge-case payloads.
2. **Green**: Implement the Zod v4 validation schema in `src/validation/*.schemas.ts`. Run the test to verify it passes.
3. **Refactor**: Clean up the schema declarations, refine custom error messages, and ensure barrel exports are updated in `index.ts`.
---
### Layer 2: Database Schema & Repository (`packages/api`)
This layer handles persistence via Mongoose schemas and Awilix-registered repositories.
1. **Red**: Write an integration test (`*.repository.test.ts`) using `mongodb-memory-server` that tests data storage, constraints, index behavior, and `householdId` filtering.
2. **Green**: Implement the Mongoose schema, compile the model, and write the Repository class methods using `.lean().exec()` on all reads. Verify the integration test passes.
3. **Refactor**: Optimize database indexes, ensure appropriate Mongoose options are set, and check for memory leaks or unclosed connections.
---
### Layer 3: Service Layer (`packages/api`)
Services contain the core business logic, including validation, authorization checks, and transactional tasks.
1. **Red**: Write unit tests (`*.service.test.ts`) using Vitest. Mock all dependencies (e.g., repositories, event emitters) using `vi.fn()`. Write tests asserting correct handling of successful cases, expected errors (`NotFoundError`, `ConflictError`, etc.), and household isolation boundaries.
2. **Green**: Create the Service class with constructor injection via Awilix. Implement only the minimum logic required to pass the test cases.
3. **Refactor**: Extract duplicate logic into helper functions, improve type safety, and simplify constructor dependencies.
---
### Layer 4: Route Layer (`packages/api`)
Fastify routes define endpoints, validate incoming payloads via Zod, resolve services, and return responses.
1. **Red**: Write a route test (`*.routes.test.ts`) using Fastify's `app.inject()`. Assert status codes, headers, and the response body structure. Mock the service layer resolved from the request DI scope (`request.diScope.resolve`).
2. **Green**: Implement the Fastify route plugin, declare path validation schemas, resolve the service from the DI container, and call it. Register the plugin.
3. **Refactor**: Optimize error mappings, clean up route paths, verify route configuration options (e.g., household scope exclusions).
---
### Layer 5: Web API Client (`packages/web`)
The client wrapper calls the backend API and handles client-side caching or state synchronization (e.g., via SWR).
1. **Red**: Write a service unit test (`*.test.ts`) using Vitest. Configure MSW (Mock Service Worker) to mock the API endpoint response. Write assertions checking payload formatting, query serialization, and error transformations.
2. **Green**: Implement the frontend service in `src/services/` using fetch. Run tests and verify MSW handlers return correctly.
3. **Refactor**: Standardize request configuration, dry up common header settings, and enhance client type-safety.
---
### Layer 6: Web UI (`packages/web`)
React components and Next.js pages display data, capture input, and handle interactions.
1. **Red**: Write component tests (`*.test.tsx`) using React Testing Library. Query elements by accessible roles (e.g., `screen.getByRole('button', { name: /save/i })`). Assert correct rendering of states (loading, empty, success) and verify user interactions fire expected service callbacks.
2. **Green**: Implement the React component (Server/Client split as appropriate). Write the bare minimum JSX/TSX to satisfy the test roles and events.
3. **Refactor**: Refine CSS structures, optimize components for re-renders, clean up accessibility attributes, and verify responsive design parameters.
---
## 4. DX Tooling Enhancements (Bridges)
To enable developers to practice TDD seamlessly, we must upgrade our test execution loop and reduce boilerplate.
### Bridge A: Immediate Watch Mode for Active Files
Running `npm run test` or `npm run test:cov` across the entire workspace takes too long for the continuous TDD loop. We need scripts to instantly watch specific directories or single files.
We will update package-level `package.json` scripts to introduce:
1. **Root package.json**:
- Add `"test:watch": "turbo run test:watch"`
2. **packages/web/package.json**:
- Add `"test:watch": "vitest"` (mirroring packages/api)
This will allow developers to run:
* `npm run test:watch -w packages/api` to start a continuous, interactive Vitest runner for API files.
* `npm run test:watch -w packages/api -- src/modules/products/products.service.test.ts` to focus only on a single service during the Red-Green cycle.
### Bridge B: Automated Mock Helpers
Creating manual mocks for every repository in every service test adds substantial boilerplate. We will introduce a standard mock utility `packages/api/src/common/test/mock-repository.ts` that dynamically mocks any repository interface.
```typescript
import { vi } from 'vitest';
export function createMockRepository<T>(repoClass: new (...args: any[]) => T): Record<keyof T, any> {
const methods = Object.getOwnPropertyNames(repoClass.prototype).filter(
(name) => name !== 'constructor' && typeof repoClass.prototype[name] === 'function'
);
const mock: Record<string, any> = {};
for (const method of methods) {
mock[method] = vi.fn();
}
return mock as Record<keyof T, any>;
}
```
This reduces the boilerplate in service tests from a manual 15-line structure to a single line:
```typescript
const mockRepo = createMockRepository(ProductsRepository);
```
---
## 5. TDD Commit & Git Conventions
To document and encourage step-by-step TDD, we will adopt atomic commit structures matching the Red-Green-Refactor steps:
1. **Red Commit**: When a new test suite is created and fails.
- `test(pantry): add failing test for spoilage calculation (RED)`
2. **Green Commit**: When the production code is completed and tests pass.
- `feat(pantry): implement spoilage calculation logic (GREEN)`
3. **Refactor Commit**: When the code is cleaned up while staying green.
- `refactor(pantry): optimize date difference utility in spoilage`
These atomic commits make peer reviews and rollbacks extremely clean and make it simple to track development momentum.
---
## 6. Guidelines Alignment & Updates
To formalize the transition, we will execute the following file updates:
1. **Update `ANTIGRAVITY.md`**:
- Modify the "Implementation Workflow" section to make TDD mandatory.
- Replace steps with the Red-Green-Refactor workflow per layer.
- Introduce the `test:watch` commands and mock helper directives.
2. **Update `docs/instructions/conventions.md`**:
- Re-orient the "Implementation Workflow" from "test-after" to TDD-first.
3. **Introduce `docs/instructions/tdd.md`**:
- A dedicated developer tutorial on TDD in the MeshiTrack codebase, explaining mocks, databases, and Next.js testing patterns in detail.
---
## 7. Immediate Action Plan
To bridge the TDD gap immediately, we will execute these concrete steps in this task:
1. **Step 1: Codebase Tooling Infrastructure**
- Add `"test:watch": "vitest"` to `packages/web/package.json`.
- Add `"test:watch": "turbo run test:watch"` to root `package.json`.
2. **Step 2: Reusable Mock Helper**
- Create `packages/api/src/common/test/mock-repository.ts` containing the automated mock builder utility.
3. **Step 3: Document TDD & Update Guidelines**
- Create `docs/instructions/tdd.md` as the definitive guide.
- Update `ANTIGRAVITY.md`'s workflow section.
- Update `docs/instructions/conventions.md`'s workflow section.
4. **Step 4: Verification**
- Verify the codebase build and tests are completely operational.