一键导入
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