QA
Quality Assurance
품질 보증. 소프트웨어 테스트와 품질 관리 활동.
Quality Assurance
품질 보증. 소프트웨어 테스트와 품질 관리 활동.
QA(Quality Assurance)는 소프트웨어가 요구사항을 충족하고, 결함 없이 동작하며, 사용자에게 좋은 경험을 제공하는지 확인하는 일련의 활동입니다. 단순한 버그 찾기를 넘어 개발 프로세스 전반의 품질을 보장합니다.
QA는 테스팅 피라미드를 기반으로 합니다: 단위 테스트(70%) > 통합 테스트(20%) > E2E 테스트(10%). 단위 테스트는 빠르고 많이, E2E 테스트는 핵심 시나리오만 자동화하는 것이 효율적입니다.
현대 QA는 Shift-Left(테스트를 개발 초기 단계로 이동)와 테스트 자동화를 강조합니다. CI/CD 파이프라인에 자동화 테스트를 통합하여 매 커밋마다 품질을 검증합니다. Selenium, Cypress, Playwright 같은 도구가 널리 사용됩니다.
실무에서 QA 엔지니어는 테스트 케이스 설계, 자동화 스크립트 작성, 성능/보안 테스트, 버그 트리아지, 릴리스 품질 게이트 관리를 담당합니다. 테스트 커버리지 80% 이상, 크리티컬 버그 0건이 일반적인 릴리스 기준입니다.
// Playwright E2E 테스트 예시
import { test, expect } from '@playwright/test';
// 테스트 설정
test.describe('로그인 기능', () => {
test.beforeEach(async ({ page }) => {
await page.goto('https://app.example.com/login');
});
// 정상 로그인 테스트
test('올바른 자격증명으로 로그인 성공', async ({ page }) => {
// Given: 로그인 페이지에 있음
await expect(page).toHaveTitle(/로그인/);
// When: 올바른 이메일과 비밀번호 입력
await page.fill('[data-testid="email-input"]', 'user@example.com');
await page.fill('[data-testid="password-input"]', 'ValidPassword123!');
await page.click('[data-testid="login-button"]');
// Then: 대시보드로 이동
await expect(page).toHaveURL(/.*dashboard/);
await expect(page.locator('[data-testid="welcome-message"]'))
.toContainText('환영합니다');
});
// 실패 케이스 테스트
test('잘못된 비밀번호로 로그인 실패', async ({ page }) => {
await page.fill('[data-testid="email-input"]', 'user@example.com');
await page.fill('[data-testid="password-input"]', 'WrongPassword');
await page.click('[data-testid="login-button"]');
// 에러 메시지 확인
await expect(page.locator('[data-testid="error-message"]'))
.toBeVisible();
await expect(page.locator('[data-testid="error-message"]'))
.toContainText('이메일 또는 비밀번호가 올바르지 않습니다');
// URL이 변경되지 않음
await expect(page).toHaveURL(/.*login/);
});
// 입력 유효성 검사 테스트
test('이메일 형식 유효성 검사', async ({ page }) => {
await page.fill('[data-testid="email-input"]', 'invalid-email');
await page.click('[data-testid="login-button"]');
await expect(page.locator('[data-testid="email-error"]'))
.toContainText('올바른 이메일 형식이 아닙니다');
});
});
// API 테스트 (Jest + Supertest)
import request from 'supertest';
import app from '../src/app';
describe('사용자 API', () => {
let authToken: string;
beforeAll(async () => {
// 테스트용 인증 토큰 발급
const res = await request(app)
.post('/api/auth/login')
.send({ email: 'test@example.com', password: 'testpass' });
authToken = res.body.token;
});
test('GET /api/users - 사용자 목록 조회', async () => {
const response = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${authToken}`)
.expect('Content-Type', /json/)
.expect(200);
expect(response.body).toHaveProperty('users');
expect(Array.isArray(response.body.users)).toBe(true);
expect(response.body.users.length).toBeGreaterThan(0);
});
test('POST /api/users - 필수 필드 누락 시 400 에러', async () => {
const response = await request(app)
.post('/api/users')
.set('Authorization', `Bearer ${authToken}`)
.send({ name: 'Test User' }) // email 필드 누락
.expect(400);
expect(response.body.error).toContain('email');
});
});
// 테스트 커버리지 설정 (jest.config.js)
module.exports = {
collectCoverageFrom: ['src/**/*.{js,ts}', '!src/**/*.d.ts'],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
};
시니어: "이번 릴리스 QA 진행 상황이 어떻게 되나요? 크리티컬 버그 있어요?"
주니어: "전체 242개 테스트 케이스 중 230개 통과했고, 크리티컬 1건, 메이저 3건 발견됐어요. 크리티컬은 결제 모듈 이슈인데 오늘 중 수정 예정입니다."
면접관: "테스트 자동화 경험에 대해 말씀해주세요."
지원자: "Playwright로 E2E 테스트 50개 케이스를 자동화했고, CI에 연동해서 PR마다 실행됩니다. 수동 테스트 시간을 하루에서 30분으로 줄였고, 회귀 버그 발견율도 40% 높아졌습니다."
리뷰어: "이 기능 테스트 코드가 없네요. 최소한 핵심 시나리오는 테스트 작성 부탁드려요."
개발자: "네, 정상 케이스와 에러 케이스 각각 단위 테스트 추가하겠습니다. 엣지 케이스도 같이 넣을게요."