원클릭으로
tdd-workflow
Test-driven development methodology and workflow for the personal blog project
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Test-driven development methodology and workflow for the personal blog project
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
| name | tdd-workflow |
| description | Test-driven development methodology and workflow for the personal blog project |
This skill defines the test-driven development process for building features in the personal blog.
Write a test that defines the desired behavior before writing implementation code.
// __tests__/lib/notion.test.ts
import { getPosts } from '@/lib/notion';
describe('getPosts', () => {
it('should return an array of published posts', async () => {
const posts = await getPosts();
expect(Array.isArray(posts)).toBe(true);
expect(posts.every(post => post.published === true)).toBe(true);
});
});
Run the test - it should FAIL ❌
Write the minimum code needed to make the test pass.
// lib/notion.ts
export async function getPosts(): Promise<NotionPost[]> {
const response = await notion.databases.query({
database_id: process.env.NOTION_DATABASE_ID!,
filter: {
property: 'Published',
checkbox: {
equals: true,
},
},
});
return response.results.map(mapNotionToPost);
}
Run the test - it should PASS ✅
Refactor the code while keeping tests green.
// lib/notion.ts - Refactored
const PUBLISHED_FILTER = {
property: 'Published',
checkbox: { equals: true },
} as const;
export async function getPosts(): Promise<NotionPost[]> {
const response = await notion.databases.query({
database_id: process.env.NOTION_DATABASE_ID!,
filter: PUBLISHED_FILTER,
sorts: [{ property: 'Date', direction: 'descending' }],
});
return response.results.map(mapNotionToPost);
}
Run the test - it should still PASS ✅
/\
/ \ E2E Tests (Few)
/____\ - Critical user flows
/ \
/ \ Integration Tests (Some)
/__________\ - API interactions, component integration
/ \
/______________\ Unit Tests (Many)
- Pure functions, utilities, helpers
Test individual functions and components in isolation.
// __tests__/lib/utils.test.ts
import { formatDate } from '@/lib/utils';
describe('formatDate', () => {
it('should format ISO date to readable format', () => {
expect(formatDate('2024-01-15')).toBe('January 15, 2024');
});
it('should handle invalid dates gracefully', () => {
expect(formatDate('invalid')).toBe('Invalid Date');
});
});
Test how components work together.
// __tests__/components/PostCard.test.tsx
import { render, screen } from '@testing-library/react';
import PostCard from '@/components/PostCard';
describe('PostCard', () => {
const mockPost = {
id: '1',
title: 'Test Post',
slug: 'test-post',
date: '2024-01-15',
summary: 'Test summary',
published: true,
};
it('should render post information correctly', () => {
render(<PostCard post={mockPost} />);
expect(screen.getByText('Test Post')).toBeInTheDocument();
expect(screen.getByText('Test summary')).toBeInTheDocument();
expect(screen.getByText('January 15, 2024')).toBeInTheDocument();
});
});
Test critical user flows end-to-end.
// e2e/blog.spec.ts (Playwright)
import { test, expect } from '@playwright/test';
test('user can view blog post', async ({ page }) => {
await page.goto('/');
await page.click('text=My First Post');
await expect(page).toHaveURL(/\/posts\/.+/);
await expect(page.locator('h1')).toContainText('My First Post');
});
Minimum Coverage: 80%
# Run tests with coverage
npm run test -- --coverage
# Coverage thresholds in package.json
{
"jest": {
"coverageThreshold": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
// __tests__/lib/notion.test.ts
import { Client } from '@notionhq/client';
jest.mock('@notionhq/client');
const mockNotion = {
databases: {
query: jest.fn(),
},
};
(Client as jest.Mock).mockImplementation(() => mockNotion);
describe('getPosts', () => {
beforeEach(() => {
mockNotion.databases.query.mockResolvedValue({
results: [
{
id: '1',
properties: {
Title: { title: [{ plain_text: 'Test' }] },
// ... other properties
},
},
],
});
});
it('should fetch posts from Notion', async () => {
const posts = await getPosts();
expect(mockNotion.databases.query).toHaveBeenCalled();
expect(posts).toHaveLength(1);
});
});
// __tests__/lib/notion.test.ts
const originalEnv = process.env;
beforeEach(() => {
process.env = {
...originalEnv,
NOTION_API_KEY: 'test-key',
NOTION_DATABASE_ID: 'test-db-id',
};
});
afterEach(() => {
process.env = originalEnv;
});
__tests__/
├── lib/
│ ├── notion.test.ts
│ └── utils.test.ts
├── components/
│ ├── PostCard.test.tsx
│ ├── Header.test.tsx
│ └── NotionRenderer.test.tsx
└── app/
└── page.test.tsx
e2e/
├── blog.spec.ts
└── navigation.spec.ts
*.test.ts or *.test.tsx*.spec.tsdescribe('ComponentName', () => {})it('should do something', () => {})For each new feature:
it('should fetch data asynchronously', async () => {
const data = await fetchData();
expect(data).toBeDefined();
});
it('should handle errors gracefully', async () => {
mockNotion.databases.query.mockRejectedValue(new Error('API Error'));
const posts = await getPosts();
expect(posts).toEqual([]);
});
import { render, screen, fireEvent } from '@testing-library/react';
it('should handle user interactions', () => {
render(<SearchBar />);
const input = screen.getByRole('textbox');
fireEvent.change(input, { target: { value: 'test' } });
expect(input).toHaveValue('test');
});
Test Behavior, Not Implementation
Keep Tests Simple
Avoid Test Interdependence
beforeEach for setupafterEach for cleanupTest Edge Cases
Fast Tests
TypeScript and JavaScript coding standards for the personal blog project
Next.js 16 + React 19 + Tailwind CSS best practices and patterns for the personal blog project
Security checklist and best practices for the personal blog project
SOC 직업 분류 기준