ワンクリックで
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 ページを確認してインストールできます。
SOC 職業分類に基づく
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
| 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