Claude-skill-registry frontend-tdd

Frontend TDD Agent. React/Next.js 기반 TDD 테스트 작성 및 구현을 담당합니다. 테스트 먼저 작성 후 구현하는 Red-Green-Refactor 사이클을 따릅니다.

install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/frontend-tdd" ~/.claude/skills/majiayu000-claude-skill-registry-frontend-tdd && rm -rf "$T"
manifest: skills/data/frontend-tdd/SKILL.md
source content

Frontend TDD Agent

역할

TDD(Test-Driven Development) 방식으로 Frontend 코드를 개발합니다. 테스트를 먼저 작성하고, 테스트를 통과하는 최소한의 코드를 구현합니다.

TDD 사이클

┌─────────────────────────────────────────────────────────────────┐
│                    1. RED (실패하는 테스트)                       │
│  - 테스트 케이스 작성                                            │
│  - 테스트 실행 → 실패 확인                                       │
└─────────────────────────────┬───────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    2. GREEN (테스트 통과)                         │
│  - 최소한의 코드 작성                                            │
│  - 테스트 실행 → 통과 확인                                       │
└─────────────────────────────┬───────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    3. REFACTOR (리팩토링)                         │
│  - 코드 개선                                                     │
│  - 테스트 실행 → 여전히 통과 확인                                 │
└─────────────────────────────────────────────────────────────────┘

테스트 스택

  • Test Runner: Jest / Vitest
  • Component Testing: React Testing Library
  • E2E Testing: Playwright
  • Mocking: MSW (Mock Service Worker)
  • Coverage: Istanbul / c8

테스트 유형

1. 컴포넌트 테스트

import { render, screen, fireEvent } from '@testing-library/react';
import { UserProfile } from './UserProfile';

describe('UserProfile', () => {
  // 렌더링 테스트
  it('should render user name', () => {
    render(<UserProfile user={{ name: 'John', email: 'john@test.com' }} />);

    expect(screen.getByText('John')).toBeInTheDocument();
  });

  // 인터랙션 테스트
  it('should call onEdit when edit button clicked', () => {
    const onEdit = jest.fn();
    render(<UserProfile user={{ name: 'John' }} onEdit={onEdit} />);

    fireEvent.click(screen.getByRole('button', { name: /edit/i }));

    expect(onEdit).toHaveBeenCalledTimes(1);
  });

  // 상태 테스트
  it('should show loading state', () => {
    render(<UserProfile isLoading />);

    expect(screen.getByTestId('loading-spinner')).toBeInTheDocument();
  });

  // 에러 상태 테스트
  it('should show error message when error occurs', () => {
    render(<UserProfile error="Failed to load" />);

    expect(screen.getByRole('alert')).toHaveTextContent('Failed to load');
  });
});

2. Hook 테스트

import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

describe('useCounter', () => {
  it('should initialize with default value', () => {
    const { result } = renderHook(() => useCounter(0));

    expect(result.current.count).toBe(0);
  });

  it('should increment count', () => {
    const { result } = renderHook(() => useCounter(0));

    act(() => {
      result.current.increment();
    });

    expect(result.current.count).toBe(1);
  });

  it('should decrement count', () => {
    const { result } = renderHook(() => useCounter(10));

    act(() => {
      result.current.decrement();
    });

    expect(result.current.count).toBe(9);
  });
});

3. API 통합 테스트 (MSW)

import { rest } from 'msw';
import { setupServer } from 'msw/node';
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';

const server = setupServer(
  rest.get('/api/users', (req, res, ctx) => {
    return res(
      ctx.json([
        { id: 1, name: 'John' },
        { id: 2, name: 'Jane' },
      ])
    );
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

describe('UserList', () => {
  it('should fetch and display users', async () => {
    render(<UserList />);

    await waitFor(() => {
      expect(screen.getByText('John')).toBeInTheDocument();
      expect(screen.getByText('Jane')).toBeInTheDocument();
    });
  });

  it('should handle API error', async () => {
    server.use(
      rest.get('/api/users', (req, res, ctx) => {
        return res(ctx.status(500));
      })
    );

    render(<UserList />);

    await waitFor(() => {
      expect(screen.getByRole('alert')).toBeInTheDocument();
    });
  });
});

테스트 명령어

# 전체 테스트 실행
npm run test

# Watch 모드
npm run test:watch

# 특정 파일 테스트
npm run test -- UserProfile

# 커버리지 리포트
npm run test:coverage

# E2E 테스트 (Playwright)
npm run test:e2e

테스트 패턴

AAA 패턴 (Arrange-Act-Assert)

it('should do something', () => {
  // Arrange - 준비
  const user = { name: 'John' };

  // Act - 실행
  render(<Component user={user} />);

  // Assert - 검증
  expect(screen.getByText('John')).toBeInTheDocument();
});

Given-When-Then 패턴

describe('Login Form', () => {
  describe('given valid credentials', () => {
    describe('when user submits form', () => {
      it('then should redirect to dashboard', async () => {
        // test code
      });
    });
  });
});

테스트 커버리지 목표

유형목표
전체> 80%
컴포넌트> 90%
Hooks> 95%
Utils> 95%

TDD 체크리스트

테스트 작성 전

  • 요구사항이 명확한가?
  • 테스트할 동작을 정의했는가?
  • 엣지 케이스를 식별했는가?

테스트 작성 시

  • 테스트명이 동작을 설명하는가?
  • 하나의 테스트는 하나만 검증하는가?
  • 테스트가 실패하는가? (RED)

구현 시

  • 최소한의 코드로 구현했는가?
  • 테스트가 통과하는가? (GREEN)

리팩토링 시

  • 중복 코드를 제거했는가?
  • 테스트가 여전히 통과하는가?
  • 코드가 읽기 쉬운가?

산출물 위치

  • 테스트 코드:
    src/**/*.test.tsx
    ,
    src/**/*.spec.tsx
  • 테스트 유틸:
    src/test/utils.tsx
  • MSW 핸들러:
    src/mocks/handlers.ts