소프트웨어 테스팅 기초: 개발자 필수 가이드
소프트웨어 테스팅의 기본 개념부터 테스트 피라미드, 주요 도구까지 체계적으로 알아봅니다.
스페이스바AI
2025년 12월 7일
9분

왜 테스팅이 중요한가?
테스트 없는 소프트웨어 개발은 기초가 약한 건물과 같습니다. 버그는 발견이 늦을수록 수정 비용이 기하급수적으로 증가합니다.
테스팅의 핵심 이점
- 조기 결함 감지: 개발 단계에서 발견 시 수정 비용 10x 절감
- 신뢰성 향상: 사용자 경험 개선
- 유지보수성: 리팩토링 시 안전망 역할
- 문서화: 테스트 코드가 곧 사용 예제
테스트 피라미드
/\
/ \ E2E (최소)
/----\
/ \ Integration
/--------\
/ \ Unit Tests (최다)
--------------
단위 테스트 (Unit Tests)
가장 많이 작성해야 할 테스트입니다.
// 예: 함수 하나를 테스트
describe('formatPrice', () => {
it('숫자를 원화 형식으로 변환한다', () => {
expect(formatPrice(1000)).toBe('1,000원');
expect(formatPrice(1234567)).toBe('1,234,567원');
});
it('0은 "무료"로 변환한다', () => {
expect(formatPrice(0)).toBe('무료');
});
});
특징:
- 빠른 실행 속도
- 독립적, 격리된 테스트
- 높은 커버리지 목표 (80%+)
통합 테스트 (Integration Tests)
컴포넌트 간 상호작용을 테스트합니다.
// 예: API와 DB 연동 테스트
describe('UserService', () => {
it('사용자 생성 후 DB에 저장된다', async () => {
const user = await userService.create({
email: 'test@example.com',
name: 'Test User'
});
const saved = await db.users.findById(user.id);
expect(saved.email).toBe('test@example.com');
});
});
E2E 테스트 (End-to-End)
실제 사용자 시나리오를 테스트합니다.
// 예: Playwright로 로그인 테스트
test('사용자가 로그인할 수 있다', async ({ page }) => {
await page.goto('/login');
await page.fill('[name="email"]', 'user@test.com');
await page.fill('[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('환영합니다');
});
테스트 유형
기능 테스트 (Functional)
"이 기능이 작동하는가?"
// 장바구니 기능 테스트
it('상품을 장바구니에 추가할 수 있다', () => {
cart.addItem({ id: 1, name: '상품A', price: 10000 });
expect(cart.items.length).toBe(1);
expect(cart.total).toBe(10000);
});
비기능 테스트 (Non-functional)
"얼마나 잘 작동하는가?"
| 유형 | 목표 | 도구 예시 |
|---|---|---|
| 성능 | 응답 시간 < 200ms | JMeter, k6 |
| 보안 | 취약점 없음 | OWASP ZAP |
| 호환성 | 브라우저 지원 | BrowserStack |
| 사용성 | 직관적 UI | 사용자 테스트 |
테스팅 방법론
블랙박스 테스팅
[입력] → [시스템] → [출력]
↑
내부 모름
- 사용자 관점에서 테스트
- 요구사항 기반
- 비개발자도 가능
화이트박스 테스팅
// 모든 코드 경로를 테스트
function calculateDiscount(price, tier) {
if (tier === 'gold') {
return price * 0.8; // 경로 1
} else if (tier === 'silver') {
return price * 0.9; // 경로 2
}
return price; // 경로 3
}
// 각 경로 테스트
it('gold 회원은 20% 할인', () => {...});
it('silver 회원은 10% 할인', () => {...});
it('일반 회원은 할인 없음', () => {...});
주요 테스팅 도구
JavaScript/TypeScript
# Jest - 단위/통합 테스트
npm install --save-dev jest @types/jest
# Playwright - E2E 테스트
npm install --save-dev @playwright/test
# Vitest - Vite 프로젝트용 (빠름)
npm install --save-dev vitest
API 테스팅
| 도구 | 특징 | 사용 사례 |
|---|---|---|
| Postman | GUI, 협업 | 수동 테스트 |
| Apidog | 통합 플랫폼 | API 전체 라이프사이클 |
| Insomnia | 가벼움 | 빠른 테스트 |
| k6 | 스크립트 기반 | 부하 테스트 |
CI/CD 통합
# GitHub Actions 예시
name: Test
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm install
- run: npm test -- --coverage
- run: npx playwright test
테스트 작성 베스트 프랙티스
1. AAA 패턴
it('사용자 생성 테스트', () => {
// Arrange (준비)
const userData = { name: 'Test', email: 'test@test.com' };
// Act (실행)
const user = createUser(userData);
// Assert (검증)
expect(user.name).toBe('Test');
expect(user.id).toBeDefined();
});
2. 하나의 테스트, 하나의 검증
// ❌ 나쁜 예
it('사용자 기능', () => {
const user = createUser({...});
expect(user.id).toBeDefined();
expect(user.createdAt).toBeDefined();
const updated = updateUser(user.id, {...});
expect(updated.name).toBe('New Name');
});
// ✅ 좋은 예
it('사용자 생성 시 ID가 생성된다', () => {...});
it('사용자 생성 시 생성일이 기록된다', () => {...});
it('사용자 이름을 수정할 수 있다', () => {...});
3. 의미있는 테스트 이름
// ❌ test1, test2
// ✅ "빈 장바구니에 상품 추가 시 총액이 상품 가격과 같다"
결론
테스팅은 선택이 아닌 필수입니다:
- 테스트 피라미드 준수: 단위 > 통합 > E2E
- 자동화: CI/CD 파이프라인에 통합
- 지속적 개선: 버그 발생 시 테스트 추가
- 균형: 100% 커버리지보다 중요 경로 우선
좋은 테스트는 코드의 안전망이자, 리팩토링의 자신감입니다.