테스트 코드 현실 적용기: Unit Testing 책을 통한 인사이트와 실무 경험

2025. 1. 29. 22:19·📂 Backend Engineering
728x90
반응형

Unit Testing 책 리뷰와 실무 적용기

들어가며

"Unit Testing: Principles, Practices, and Patterns"(Vladimir Khorikov 저) 책을 읽고 실무에 적용하면서 느낀 점을 정리해보았습니다. 테스트 코드를 작성하는 여러 방법론과 실제 현장에서의 적용 과정에서 배운 점들을 공유하고자 합니다.
(실은 읽다가 어려워서 누락된 내용도 꽤 많아요 ㅎㅎ이해하고 넘어가주세요)

책의 핵심 내용

테스트의 기본 원칙

  1. 테스트는 비즈니스 시나리오를 반영해야 함
  2. 테스트 가독성이 중요
  3. 테스트가 구현이 아닌 동작을 검증해야 함

단위 테스트의 정의

  1. 작은 코드조각을 검증하고
  2. 빠르게 수행하고
  3. 격리된 방식으로 처리하는 자동화된 테스트

고전파 vs 런던파

격리 문제로 인한 두 학파의 차이

  • 고전파: 실제 의존성을 사용
  • 런던파: 모든 의존성을 mock으로 대체
// 고전파 예시
test('주문 생성', () => {
  // 실제 의존성 사용
  const db = new Database();
  const payment = new Payment();
  
  const order = new Order(db, payment);
  order.create();
  
  expect(db.findOrder()).toBeDefined();
});

// 런던파 예시
test('주문 생성', () => {
  // 모든 의존성을 mock으로 대체
  const mockDB = { save: jest.fn() };
  const mockPayment = { process: jest.fn() };
  
  const order = new Order(mockDB, mockPayment);
  order.create();
  
  expect(mockDB.save).toHaveBeenCalled();
});

 

 

두 학파의 주요 차이점

  1. 테스트 주도 개발을 통한 시스템 설계 방식
  2. 과도한 명세 문제

테스트 주도 개발(TDD)

TDD의 핵심 원칙

  1. 실패하는 테스트를 먼저 작성
  2. 코드가 완벽하지 않아도 됨
  3. 읽기 쉽고 유지보수가 용이하도록 작성

실무 적용 경험

프로젝트 진행 과정

  1. 기획 완료 후 프론트엔드/백엔드 동시 개발 시작
  2. 데이터 모델 설계
  3. Swagger를 통한 API 문서화 및 소통
  4. 개발 완료 후 API 테스트 및 QA

실무에서 겪은 어려움과 해결책

  • Swagger 문서 작성이 TDD로 인해 지연되는 문제 발생
  • 해결책: Swagger용 mock 객체 먼저 생성 후 TDD 진행
  • 현재는 API 개발 후 AI 도움을 받아 테스트 케이스 작성

테스트 유형별 특징

통합 테스트와 E2E 테스트

  • 통합 테스트: 여러 컴포넌트간 상호작용 검증
  • E2E 테스트: 전체 시스템 흐름 테스트

테스트 대역(Test Double)의 이해

  1. 더미(Dummy): 단순 전달용 객체
  2. 스텁(Stub): 미리 준비된 응답 제공
  3. 스파이(Spy): 메서드 호출 기록
  4. 목(Mock): 예상 동작 검증
  5. 페이크(Fake): 간소화된 실제 구현체

블랙박스/화이트박스 테스트

  • 블랙박스: 내부 구조를 모르는 상태에서 테스트
  • 화이트박스: 내부 구조를 아는 상태에서 테스트

테스트 피라미드

테스트 코드의 이상적인 구조

단위 테스트 >> 통합 테스트 >> E2E 테스트 순으로 작성 권장

피라미드 구조를 권장하는 이유

  1. E2E 테스트의 한계
    • 높은 유지보수 비용
    • 긴 실행 시간
    • 불안정성 (깨지기 쉬움)
  2. 단위 테스트의 장점
    • 빠른 실행 속도
    • 안정적인 결과
    • 문제 발생 지점 특정 용이

테스트 유형별 코드 예시

1. 단위 테스트

// 독립적인 단위 기능 테스트
test('이메일 유효성 검사', () => {
  const validator = new EmailValidator();
  expect(validator.isValid('test@email.com')).toBe(true);
  expect(validator.isValid('invalid-email')).toBe(false);
});

 

2. 통합 테스트

// 여러 컴포넌트 연동 테스트
test('유저 생성 및 이메일 발송', async () => {
  const userService = new UserService(database, emailService);
  const user = await userService.createUser({
    email: 'test@email.com',
    name: 'John'
  });
  
  // 실제 DB 저장 및 이메일 발송 검증
  const savedUser = await database.findUser(user.id);
  expect(savedUser).toBeDefined();
});

3. E2E 테스트

// 전체 시스템 흐름 테스트
test('회원가입 프로세스', async () => {
  // 회원가입
  const signupRes = await request(app)
    .post('/api/signup')
    .send({
      email: 'test@email.com',
      password: 'password123'
    });
  
  // 로그인 및 토큰 발급
  const loginRes = await request(app)
    .post('/api/login')
    .send({
      email: 'test@email.com',
      password: 'password123'
    });
  
  // 인증이 필요한 API 호출
  const profileRes = await request(app)
    .get('/api/profile')
    .set('Authorization', loginRes.body.token);
  
  expect(profileRes.status).toBe(200);
});
 
 
 

테스트 접근 방식

블랙박스 테스트

  • 내부 구현을 모르는 상태에서 테스트
  • What에 집중 (어떤 결과가 나와야 하는가)
  • 명세와 요구사항 기반 테스트

화이트박스 테스트

  • 내부 구현을 아는 상태에서 테스트
  • How까지 고려 (어떻게 동작하는가)
  • 더 철저한 테스트 가능

테스트 대역의 활용

테스트 대역의 다섯 가지 유형과 특징:

  1. 더미(Dummy)
    • 가장 단순한 형태
    • 실제로 사용되지 않고 전달만 됨
  2. 스텁(Stub)
    • 미리 준비된 응답 제공
    • 상태 기반 테스트에 활용
  3. 스파이(Spy)
    • 호출 정보를 기록
    • 실제 객체처럼 동작하면서 정보 수집
  4. 목(Mock)
    • 기대하는 동작 정의 및 검증
    • 행위 검증에 중점
  5. 페이크(Fake)
    • 실제 구현을 단순화한 대체제
    • 실제와 유사하게 동작

각 대역의 선택은 테스트의 목적과 상황에 따라 결정해야 합니다.

실무 적용 사례

현재 팀의 테스트 전략

  1. 단위 테스트
    • 서비스 레이어의 비즈니스 로직 검증
    • DB 의존성 없는 순수 로직 테스트
  2. E2E 테스트
    • API 엔드포인트 검증
    • 실제 사용자 시나리오 기반 테스트

테스트 작성 프로세스

  1. API 설계 및 개발
  2. 시나리오 도출
  3. 테스트 케이스 작성
  4. AI 도구를 활용한 테스트 보완
728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'📂 Backend Engineering' 카테고리의 다른 글

(공식문서) NestJS Pipes와 queryString, Body가 데이터 처리하는 방법  (1) 2025.02.18
NestJS Exception Filters 완벽 정리 (라이프사이클부터 내부 동작까지)  (0) 2025.02.12
[백엔드]기프티콘 도메인에 대한 이해 & 정리 & 회고  (0) 2024.05.11
[Cursor 페이지네이션] nestJS + Prisma - cursor 페이지네이션 방식 (성능 최적화, 직접 cursor 구현)  (1) 2024.03.31
RDB와 NoSql 비교 및 장점 | AWS에서 제공하는 NoSql은?  (0) 2024.03.15
'📂 Backend Engineering' 카테고리의 다른 글
  • (공식문서) NestJS Pipes와 queryString, Body가 데이터 처리하는 방법
  • NestJS Exception Filters 완벽 정리 (라이프사이클부터 내부 동작까지)
  • [백엔드]기프티콘 도메인에 대한 이해 & 정리 & 회고
  • [Cursor 페이지네이션] nestJS + Prisma - cursor 페이지네이션 방식 (성능 최적화, 직접 cursor 구현)
foodev
foodev
이것저것 개발과 이것저것 리뷰 합니다.
    반응형
    250x250
  • foodev
    개발 개맛집
    foodev
  • 전체
    오늘
    어제
    • 분류 전체보기 (104) N
      • ⭐ Featured (4)
      • 📂 Backend Engineering (36)
      • 📂 Troubleshooting & Ops (10)
      • 📂 Infra & System (7) N
      • 📂 Reflections (21)
        • Year-in-Review (5)
        • Work & Career (10)
        • Lessons Learned (6)
      • 📂 Team Journal (10)
      • 📂 Archive (16)
  • 블로그 메뉴

    • 홈
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    스냅샷과 히스토리
    토이프로젝트개발일지
    해피해킹 방향키
    스냅샷과히스토리성 차이
    validation failed (numeric string is expected)
    typedi 동작원리
    서이추
    QueryDSL
    di의존성
    해피해킹 카라비너
    db 초기화
    db 날림
    해피해킹 키매핑
    typedi란
    해피해킹 커스텀
    di란
    JPA
    Azure 로그 최소 저장 30일
    di동작원리
    창업패키지후기
    githubaction 라벨 배포
    githubaction 라벨 ci/cd
    nestjs pipe body
    Azure log 비용 줄이기
    해피해킹 꿀팁
    nestjs pipe
    인프라 로그 저장 비용 감소하는 방법
    개발썰
    azure ci/cd
    db 날린 썰
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
foodev
테스트 코드 현실 적용기: Unit Testing 책을 통한 인사이트와 실무 경험
상단으로

티스토리툴바