본문으로 건너뛰기

Cursor로 테스트 주도 개발하기: 단위, 통합 및 E2E

Cursor Testing Guide

테스트는 코드 품질과 배포에 대한 신뢰를 유지하는 데 필수적입니다. Cursor의 AI 기능은 테스트 생성, 엣지 케이스 제안, 더 나은 커버리지 달성을 도와 테스트 주도 개발(TDD)을 더 쉽게 접근할 수 있게 합니다. 이 가이드에서는 단위, 통합, 엔드투엔드 테스트 워크플로우를 다룹니다.

사전 요구 사항​

시작하기 전에 다음이 필요합니다:

  • Cursor 설치 (최신 버전)
  • Node.js 설치
  • 테스트 개념에 대한 기본 이해

Jest를 활용한 단위 테스트​

Jest 설정​

Jest 및 관련 패키지 설치:

npm install --save-dev jest @types/jest ts-jest

jest.config.js 구성:

module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.test.ts'],
collectCoverageFrom: [
'src/**/*.ts',
'!src/**/*.d.ts',
'!src/**/*.test.ts',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};

Cursor로 단위 테스트 생성​

함수를 선택하고 Cursor에게 테스트를 생성하도록 요청하세요:

// src/utils/calculator.ts
export const calculateDiscount = (
price: number,
discountPercent: number,
maxDiscount?: number
): number => {
if (price < 0 || discountPercent < 0 || discountPercent > 100) {
throw new Error('Invalid input');
}

const discount = (price * discountPercent) / 100;
const finalDiscount = maxDiscount ? Math.min(discount, maxDiscount) : discount;

return Number((price - finalDiscount).toFixed(2));
};

프롬프트:

이 함수에 대한 포괄적인 Jest 단위 테스트를 작성해주세요. 다음을 포함해야 합니다:
- 정상 케이스 테스트
- 엣지 케이스 (0, 음수, 경계값)
- 오류 케이스
- maxDiscount 매개변수에 대한 테스트

Cursor가 생성한 결과:

// src/utils/__tests__/calculator.test.ts
import { calculateDiscount } from '../calculator';

describe('calculateDiscount', () => {
describe('happy path', () => {
it('should calculate 10% discount correctly', () => {
expect(calculateDiscount(100, 10)).toBe(90);
});

it('should calculate 50% discount correctly', () => {
expect(calculateDiscount(200, 50)).toBe(100);
});
});

describe('maxDiscount parameter', () => {
it('should apply maxDiscount when discount exceeds it', () => {
expect(calculateDiscount(1000, 50, 100)).toBe(900);
});

it('should ignore maxDiscount when discount is smaller', () => {
expect(calculateDiscount(100, 10, 50)).toBe(90);
});
});

describe('edge cases', () => {
it('should return original price with 0% discount', () => {
expect(calculateDiscount(100, 0)).toBe(100);
});

it('should return 0 with 100% discount', () => {
expect(calculateDiscount(100, 100)).toBe(0);
});

it('should handle decimal prices', () => {
expect(calculateDiscount(99.99, 10)).toBe(89.99);
});
});

describe('error cases', () => {
it('should throw error for negative price', () => {
expect(() => calculateDiscount(-10, 10)).toThrow('Invalid input');
});

it('should throw error for negative discount', () => {
expect(() => calculateDiscount(100, -5)).toThrow('Invalid input');
});

it('should throw error for discount over 100%', () => {
expect(() => calculateDiscount(100, 101)).toThrow('Invalid input');
});
});
});

React 컴포넌트 테스트​

// src/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
it('renders with correct text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});

it('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});

it('is disabled when loading', () => {
render(<Button isLoading>Loading</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});

현대적인 프로젝트를 위한 Vitest​

Vitest 구성​

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
test: {
globals: true,
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
},
});

Vitest 테스트 예시​

// src/utils/formatDate.test.ts
import { describe, it, expect } from 'vitest';
import { formatDate } from './formatDate';

describe('formatDate', () => {
it('formats ISO date to readable string', () => {
const result = formatDate('2024-01-15');
expect(result).toBe('January 15, 2024');
});

it('handles invalid dates gracefully', () => {
expect(() => formatDate('invalid')).toThrow();
});
});
Vitest vs Jest

Vitest는 네이티브 TypeScript 지원, 더 빠른 실행, 더 나은 ESM 호환성을 제공합니다. 새 프로젝트에는 Vitest를 사용하고, 기존 테스트 스위트가 있는 경우 Jest를 유지하세요.

통합 테스트​

API 통합 테스트​

// tests/integration/users.test.ts
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { createApp } from '../../src/app';
import { setupTestDB, teardownTestDB } from '../helpers/database';

const app = createApp();

describe('Users API Integration', () => {
beforeAll(async () => {
await setupTestDB();
});

afterAll(async () => {
await teardownTestDB();
});

describe('POST /api/users', () => {
it('creates a user and persists to database', async () => {
const response = await app.request('/api/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
name: 'Integration Test',
password: 'password123',
}),
});

expect(response.status).toBe(201);
const body = await response.json();
expect(body.data.email).toBe('[email protected]');
expect(body.data.id).toBeDefined();
});
});
});

데이터베이스 통합 설정​

// tests/helpers/database.ts
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

export async function setupTestDB() {
// Run migrations
// Seed test data
}

export async function teardownTestDB() {
// Clean up test data
await prisma.$executeRaw`TRUNCATE TABLE users CASCADE`;
await prisma.$disconnect();
}

엔드투엔드 테스트​

Cypress 설정​

npm install --save-dev cypress
npx cypress open

Cursor로 E2E 테스트 작성​

// cypress/e2e/auth.cy.js
describe('Authentication Flow', () => {
beforeEach(() => {
cy.visit('/login');
});

it('allows user to log in with valid credentials', () => {
cy.get('[data-testid="email-input"]').type('[email protected]');
cy.get('[data-testid="password-input"]').type('password123');
cy.get('[data-testid="login-button"]').click();

cy.url().should('include', '/dashboard');
cy.get('[data-testid="welcome-message"]').should('contain', 'Welcome');
});

it('shows error for invalid credentials', () => {
cy.get('[data-testid="email-input"]').type('[email protected]');
cy.get('[data-testid="password-input"]').type('wrongpassword');
cy.get('[data-testid="login-button"]').click();

cy.get('[data-testid="error-message"]')
.should('be.visible')
.and('contain', 'Invalid credentials');
});
});

Playwright 대안​

// tests/e2e/checkout.spec.ts
import { test, expect } from '@playwright/test';

test('user can complete checkout', async ({ page }) => {
await page.goto('/products');

// Add item to cart
await page.click('[data-testid="add-to-cart-1"]');
await page.click('[data-testid="cart-icon"]');

// Proceed to checkout
await page.click('[data-testid="checkout-button"]');

// Fill shipping info
await page.fill('[name="address"]', '123 Test St');
await page.fill('[name="city"]', 'Test City');
await page.selectOption('[name="country"]', 'US');

// Complete order
await page.click('[data-testid="place-order"]');

await expect(page.locator('[data-testid="order-confirmation"]')).toBeVisible();
});

테스트 커버리지 및 품질​

커버리지 보고서​

커버리지 생성 및 보기:

# Jest
npm test -- --coverage

# Vitest
npx vitest --coverage

AI 기반 커버리지 개선​

커버리지 보고서를 Cursor와 공유하세요:

테스트 커버리지가 65%입니다. 커버되지 않은 줄은 다음과 같습니다:
[커버리지 보고서 붙여넣기]

이 엣지 케이스를 커버할 테스트를 제안해주세요.

테스트 모범 사례​

사례설명
AAA 패턴Arrange, Act, Assert - 모든 테스트를 명확하게 구조화
테스트당 하나의 어설션테스트를 집중적이고 읽기 쉽게 유지
설명적인 이름테스트 이름은 문서처럼 읽혀야 함
외부 의존성 모킹데이터베이스, API를 모킹하여 단위를 격리
동작 테스트, 구현 테스트 금지리팩토링에 안전한 테스트는 결과를 검증
data-testid 사용E2E 테스트를 위한 안정적인 선택자 추가

테스트 유틸리티 및 도우미​

// src/test/factories.ts
import { faker } from '@faker-js/faker';

export const createUserFixture = (overrides = {}) => ({
id: faker.string.uuid(),
email: faker.internet.email(),
name: faker.person.fullName(),
createdAt: new Date(),
...overrides,
});

export const createProductFixture = (overrides = {}) => ({
id: faker.string.uuid(),
name: faker.commerce.productName(),
price: Number(faker.commerce.price()),
...overrides,
});

Cursor에서 테스트 실행​

터미널 통합​

# Run all tests
npm test

# Run with watch mode
npm test -- --watch

# Run specific file
npm test -- src/utils/calculator.test.ts

# Run with coverage
npm test -- --coverage

테스트 탐색기​

Cursor는 VS Code 테스트 탐색기를 지원합니다:

  1. 테스트 사이드바 열기
  2. 파일별로 정리된 모든 테스트 보기
  3. 클릭하여 개별 테스트 실행
  4. 인라인으로 통과/실패 상태 보기

지속적 통합​

GitHub Actions 예시​

# .github/workflows/test.yml
name: Tests
on: [push, pull_request]

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm test
- run: npm run test:e2e

빠른 참고​

작업Cursor 프롬프트
단위 테스트 생성"Write Jest tests for this function with edge cases"
외부 API 모킹"Create a mock for this API client using MSW"
실패한 테스트 수정"This test is failing with [error]. Fix it."
커버리지 개선"Suggest tests to improve coverage for this file"
E2E 테스트 흐름"Write a Cypress test for the user checkout flow"

관련 리소스​

마지막 업데이트: 2025년 7월