🔧 DevOps

테스트

Testing

코드가 예상대로 동작하는지 검증. 유닛, 통합, E2E 테스트로 구분. TDD는 테스트 우선 개발.

상세 설명

테스트(Testing)는 소프트웨어가 예상대로 동작하는지 검증하는 활동입니다. 버그를 조기에 발견하고, 리팩토링 시 안전망을 제공하며, 코드의 동작을 문서화합니다. 품질 보증(QA)의 핵심입니다.

테스트 피라미드에서 유닛 테스트(단위), 통합 테스트, E2E 테스트(종단간)로 계층화됩니다. 유닛 테스트가 가장 많고 빠르며, E2E로 갈수록 적고 느립니다. 70/20/10 비율을 권장합니다.

TDD(Test-Driven Development)는 테스트를 먼저 작성하고 코드를 구현합니다. Red(실패) -> Green(통과) -> Refactor(개선) 사이클로 진행합니다. BDD(Behavior-Driven Development)는 사용자 행동 관점에서 Given-When-Then으로 테스트를 기술합니다.

실무에서는 CI에서 모든 테스트를 자동 실행합니다. 커버리지 80% 이상을 목표로 하되, 숫자보다 중요 로직의 테스트 여부가 중요합니다. 테스트 속도를 위해 모킹을 활용하고, 플레이키(flaky) 테스트는 즉시 수정합니다.

코드 예제

// Jest 유닛 테스트 예제
describe('calculatePrice', () => {
  // 기본 케이스
  it('should calculate price with no discount', () => {
    const result = calculatePrice(100, 0);
    expect(result).toBe(100);
  });

  // 할인 적용
  it('should apply percentage discount correctly', () => {
    const result = calculatePrice(100, 20);
    expect(result).toBe(80);
  });

  // 엣지 케이스
  it('should handle 100% discount', () => {
    const result = calculatePrice(100, 100);
    expect(result).toBe(0);
  });

  // 에러 케이스
  it('should throw error for negative price', () => {
    expect(() => calculatePrice(-100, 10)).toThrow('Invalid price');
  });
});

// 비동기 테스트
describe('UserService', () => {
  it('should fetch user by id', async () => {
    const mockUser = { id: 1, name: 'John' };
    jest.spyOn(api, 'getUser').mockResolvedValue(mockUser);

    const result = await userService.getUserById(1);

    expect(result).toEqual(mockUser);
    expect(api.getUser).toHaveBeenCalledWith(1);
  });
});

// Integration Test (Supertest)
describe('POST /api/orders', () => {
  it('should create order and return 201', async () => {
    const orderData = { productId: 1, quantity: 2 };

    const response = await request(app)
      .post('/api/orders')
      .send(orderData)
      .set('Authorization', `Bearer ${testToken}`);

    expect(response.status).toBe(201);
    expect(response.body.orderId).toBeDefined();
  });
});

// package.json scripts
// "scripts": {
//   "test": "jest",
//   "test:watch": "jest --watch",
//   "test:coverage": "jest --coverage",
//   "test:ci": "jest --ci --coverage --reporters=default --reporters=jest-junit"
// }

실무에서 이렇게 말해요

시니어: "결제 로직 변경했는데, 테스트 커버리지 어떤가요?"

주니어: "해당 모듈 95%이고, 경계값 케이스랑 에러 핸들링 테스트 다 추가했습니다."

면접관: "테스트 피라미드에서 각 계층의 특징을 설명해주세요."

지원자: "유닛 테스트는 빠르고 격리된 함수/클래스 단위를 테스트합니다. 통합 테스트는 모듈 간 상호작용을, E2E 테스트는 실제 사용자 시나리오를 검증합니다. 하위 계층이 많고 빠를수록 디버깅이 쉽습니다."

리뷰어: "이 테스트는 외부 API를 실제로 호출하는데, CI에서 불안정하지 않을까요?"

개발자: "mockResolvedValue로 모킹 처리하겠습니다. 실제 API 테스트는 별도 integration 스위트로 분리할게요."

주의사항

더 배우기