Automatically generate and run tests with detailed reports
Generates and runs comprehensive unit, integration, or E2E tests with detailed reports.
/plugin marketplace add lightsoft-dev/claude-plugin-for-dev/plugin install test-generator@lightsoft-plugins테스트 코드를 자동으로 생성하고 실행한 뒤, 상세한 결과 리포트를 제공합니다.
사용자에게 테스트할 파일을 물어보세요:
src/utils/calculator.js, components/Button.tsx파일이 선택되면 Read 도구로 파일 내용을 읽으세요.
사용자에게 다음 중 선택하도록 안내:
사용자의 선택을 기록하세요.
프로젝트의 package.json을 읽고 설치된 테스트 도구를 확인:
유닛/통합 테스트 프레임워크:
jest: Jestvitest: Vitestmocha + chai: Mocha@testing-library/react: React Testing Library@testing-library/vue: Vue Testing LibraryE2E 테스트 프레임워크:
cypress: Cypress@playwright/test: Playwrightpuppeteer: Puppeteer프레임워크가 없는 경우:
npm install -D [프레임워크]읽은 파일 내용을 분석하여 다음을 추출:
예시 분석 결과:
파일: src/utils/math.js
함수:
1. add(a, b) - 두 수를 더함
2. subtract(a, b) - 두 수를 뺌
3. multiply(a, b) - 두 수를 곱함
4. divide(a, b) - 나눗셈 (0으로 나누기 체크 필요)
선택한 테스트 유형과 프레임워크에 맞는 테스트 코드를 생성:
대상 파일: src/utils/math.js
export function add(a, b) {
return a + b;
}
export function divide(a, b) {
if (b === 0) throw new Error('Division by zero');
return a / b;
}
생성할 테스트: src/utils/math.test.js (Jest/Vitest)
import { describe, test, expect } from '@jest/globals';
import { add, divide } from './math';
describe('Math Utils', () => {
describe('add()', () => {
test('두 양수를 더한다', () => {
expect(add(2, 3)).toBe(5);
});
test('음수를 처리한다', () => {
expect(add(-5, 3)).toBe(-2);
});
test('0을 처리한다', () => {
expect(add(0, 0)).toBe(0);
});
test('소수점을 처리한다', () => {
expect(add(0.1, 0.2)).toBeCloseTo(0.3);
});
test('매우 큰 수를 처리한다', () => {
expect(add(1e10, 1e10)).toBe(2e10);
});
});
describe('divide()', () => {
test('정상적인 나눗셈을 수행한다', () => {
expect(divide(10, 2)).toBe(5);
});
test('소수 결과를 반환한다', () => {
expect(divide(7, 2)).toBe(3.5);
});
test('0으로 나누면 에러를 던진다', () => {
expect(() => divide(10, 0)).toThrow('Division by zero');
});
test('음수 나눗셈을 처리한다', () => {
expect(divide(-10, 2)).toBe(-5);
});
test('0을 나누면 0을 반환한다', () => {
expect(divide(0, 5)).toBe(0);
});
});
});
대상: API + Database 통합
// src/services/userService.integration.test.js
import { describe, test, expect, beforeAll, afterAll } from '@jest/globals';
import { UserService } from './userService';
import { setupTestDatabase, cleanupTestDatabase } from '../test-utils/db';
describe('UserService Integration Tests', () => {
let userService;
let testDb;
beforeAll(async () => {
testDb = await setupTestDatabase();
userService = new UserService(testDb);
});
afterAll(async () => {
await cleanupTestDatabase(testDb);
});
describe('사용자 생성 및 조회', () => {
test('새 사용자를 생성하고 조회할 수 있다', async () => {
const userData = {
name: '홍길동',
email: 'hong@test.com'
};
const created = await userService.createUser(userData);
expect(created).toHaveProperty('id');
expect(created.name).toBe(userData.name);
const found = await userService.findById(created.id);
expect(found).toEqual(created);
});
test('중복 이메일은 거부된다', async () => {
await userService.createUser({ name: 'A', email: 'dup@test.com' });
await expect(
userService.createUser({ name: 'B', email: 'dup@test.com' })
).rejects.toThrow('Email already exists');
});
});
describe('사용자 업데이트', () => {
test('사용자 정보를 수정할 수 있다', async () => {
const user = await userService.createUser({
name: '김철수',
email: 'kim@test.com'
});
const updated = await userService.updateUser(user.id, {
name: '김영희'
});
expect(updated.name).toBe('김영희');
expect(updated.email).toBe('kim@test.com');
});
});
});
대상: 로그인 플로우
// e2e/login.spec.js
import { test, expect } from '@playwright/test';
test.describe('로그인 플로우', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000');
});
test('성공적인 로그인', async ({ page }) => {
// 로그인 페이지로 이동
await page.click('text=로그인');
await expect(page).toHaveURL(/.*login/);
// 폼 작성
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
// 제출
await page.click('button[type="submit"]');
// 대시보드로 리다이렉트 확인
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('h1')).toContainText('환영합니다');
});
test('잘못된 비밀번호 에러 처리', async ({ page }) => {
await page.click('text=로그인');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'wrongpassword');
await page.click('button[type="submit"]');
// 에러 메시지 확인
await expect(page.locator('.error-message'))
.toContainText('비밀번호가 올바르지 않습니다');
});
test('이메일 유효성 검사', async ({ page }) => {
await page.click('text=로그인');
await page.fill('input[name="email"]', 'invalid-email');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
// HTML5 validation 또는 커스텀 에러
const emailInput = page.locator('input[name="email"]');
await expect(emailInput).toHaveAttribute('aria-invalid', 'true');
});
});
// components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, test, expect, vi } from 'vitest';
import Button from './Button';
describe('Button 컴포넌트', () => {
test('텍스트를 올바르게 렌더링한다', () => {
render(<Button>클릭</Button>);
expect(screen.getByRole('button')).toHaveTextContent('클릭');
});
test('클릭 이벤트를 처리한다', () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>클릭</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
test('disabled 상태를 처리한다', () => {
render(<Button disabled>클릭</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
test('variant prop에 따라 올바른 클래스를 적용한다', () => {
const { rerender } = render(<Button variant="primary">클릭</Button>);
expect(screen.getByRole('button')).toHaveClass('btn-primary');
rerender(<Button variant="secondary">클릭</Button>);
expect(screen.getByRole('button')).toHaveClass('btn-secondary');
});
});
프로젝트 구조에 맞게 테스트 파일 저장 위치 결정:
옵션 1: 같은 폴더 (권장)
src/utils/
├── math.js
└── math.test.js
옵션 2: tests 폴더
src/utils/
├── math.js
└── __tests__/
└── math.test.js
옵션 3: 별도 tests 폴더
src/utils/math.js
tests/unit/utils/math.test.js
E2E 테스트:
e2e/
├── login.spec.js
└── signup.spec.js
사용자에게 선호하는 위치를 물어보거나, 기존 프로젝트 패턴을 따르세요.
Write 도구를 사용하여 생성된 테스트 코드를 파일로 저장:
.test.js, .spec.js 등의 확장자로 저장생성한 테스트를 자동으로 실행:
package.json의 scripts 확인:
{
"scripts": {
"test": "jest",
"test:unit": "vitest run",
"test:e2e": "playwright test"
}
}
단위/통합 테스트:
npm test -- [테스트파일경로]
# 또는
npx jest src/utils/math.test.js
# 또는
npx vitest run src/utils/math.test.js
E2E 테스트:
npm run test:e2e
# 또는
npx playwright test e2e/login.spec.js
커버리지 포함:
npm test -- --coverage
Bash 도구를 사용하여 테스트 실행하고 결과를 캡처하세요.
테스트 실행 결과를 분석하여 사용자에게 상세한 리포트를 제공:
# 🧪 테스트 결과 리포트
## 📋 테스트 정보
- **대상 파일**: src/utils/math.js
- **테스트 파일**: src/utils/math.test.js
- **테스트 유형**: 단위 테스트 (Unit Test)
- **프레임워크**: Jest 29.5.0
- **실행 시간**: 2024-11-14 14:30:25
---
## 📊 전체 결과
✅ **통과**: 9개
❌ **실패**: 1개
⏭️ **스킵**: 0개
**성공률**: 90% (9/10)
---
## 🔍 상세 테스트 케이스
### ✅ add() 함수 (5/5 통과)
#### [PASS] 두 양수를 더한다
- **입력**: add(2, 3)
- **예상**: 5
- **결과**: 5 ✓
- **테스트 항목**: 기본 덧셈 연산
#### [PASS] 음수를 처리한다
- **입력**: add(-5, 3)
- **예상**: -2
- **결과**: -2 ✓
- **테스트 항목**: 음수 처리
#### [PASS] 0을 처리한다
- **입력**: add(0, 0)
- **예상**: 0
- **결과**: 0 ✓
- **테스트 항목**: 경계값 (0)
#### [PASS] 소수점을 처리한다
- **입력**: add(0.1, 0.2)
- **예상**: ~0.3 (부동소수점 오차 허용)
- **결과**: 0.30000000000000004 ✓
- **테스트 항목**: 부동소수점 연산
#### [PASS] 매우 큰 수를 처리한다
- **입력**: add(1e10, 1e10)
- **예상**: 2e10
- **결과**: 20000000000 ✓
- **테스트 항목**: 큰 수 처리
---
### ⚠️ divide() 함수 (4/5 통과, 1개 실패)
#### [PASS] 정상적인 나눗셈을 수행한다
- **입력**: divide(10, 2)
- **예상**: 5
- **결과**: 5 ✓
- **테스트 항목**: 기본 나눗셈
#### [PASS] 소수 결과를 반환한다
- **입력**: divide(7, 2)
- **예상**: 3.5
- **결과**: 3.5 ✓
- **테스트 항목**: 소수 결과
#### [FAIL] 0으로 나누면 에러를 던진다 ❌
- **입력**: divide(10, 0)
- **예상**: Error('Division by zero')
- **실제 결과**: Infinity
- **테스트 항목**: 에러 핸들링 (0으로 나누기)
- **실패 원인**: 함수가 에러를 던지지 않고 Infinity를 반환함
**스택 트레이스:**
Error: Expected function to throw an error, but it returned Infinity at Object.<anonymous> (src/utils/math.test.js:32:7)
**수정 제안:**
```javascript
export function divide(a, b) {
if (b === 0) {
throw new Error('Division by zero');
}
return a / b;
}
| 항목 | 비율 | 커버된 라인/전체 라인 |
|---|---|---|
| Statements | 95% | 19/20 |
| Branches | 87.5% | 7/8 |
| Functions | 100% | 2/2 |
| Lines | 95% | 19/20 |
라인 15: throw new Error('Division by zero')
추가 테스트 케이스
add(): NaN, null, undefined 입력 처리divide(): Infinity 입력 처리성능 테스트
문서화
❌ 실패한 테스트 수정
# divide 함수 수정 후 재실행:
npm test src/utils/math.test.js
📝 추가 테스트 작성
🚀 CI/CD 통합
모든 테스트가 통과하도록 코드를 수정해주세요! 💪
---
### 10. 추가 기능 (선택적)
#### A. 자동 수정 제안
실패한 테스트에 대해 코드 수정 제안:
- 문제 원인 분석
- 수정 코드 예시
- 사용자가 원하면 자동 수정
#### B. 테스트 커버리지 개선
📊 커버리지 개선 제안:
현재: 87.5% 목표: 95%+
추가가 필요한 테스트:
#### C. 스냅샷 테스트 (React/Vue)
```javascript
test('컴포넌트 렌더링 스냅샷', () => {
const { container } = render(<Button>클릭</Button>);
expect(container.firstChild).toMatchSnapshot();
});