Code/Test

[단위 테스트] 8-1. 통합 테스트를 하는 이유: 통합 테스트

noahkim_ 2025. 2. 28. 10:29

블라디미르 코리코프 님의 "단위 테스트" 책을 정리한 포스팅입니다.

 

1. 통합 테스트는 무엇인가?

  • 단위 테스트만으로 전체 시스템이 잘 동작하는지 확신할 수 없음
  • 각 부분이 외부 시스템과 어떻게 통합되는지 확인해야 함
  • 비즈니스 로직을 격리된 상태로 확인하는 것만으로는 충분하지 않음

 

통합 테스트

  • 여러 컴포넌트가 실제처럼 함께 작동하는지 확인하는 테스트
  • 외부 시스템과 통합된 상태에서 작동 여부를 검증함
구분 설명
대상 외부 시스템과 상호작용하는 코드 (컨트롤러, 애플리케이션 서비스 등)
목적
시스템이 외부 의존성과 통합된 상태에서 올바르게 작동하는지 검증
검증 범위
단일 동작 단위를 검증 (예: 하나의 API 요청 처리 흐름)
속도 단위 테스트보다 느릴 수 있음
테스트 격리
테스트 환경을 격리하여 의도한 통합 동작만 검증

 

단위 테스트와 통합 테스트의 비율

  • 단위 테스트로 가능한 한 많이 비즈니스 시나리오의 예외 상황을 확인
  • 통합 테스트로 주요 흐름과 단위 테스트가 다루지 못하는 기타 예외 사항 다루기

 

빠른 실패

  • 통합 테스트는 일반 흐름 + 예외 흐름을 테스트해야 함
  • 하지만 앱이 아예 작동하지 못하게 만드는 극단적인 실패는 테스트 대상이 아님 (예: DB 접속 안 됨)
  • 이런 극단적인 상황은 실행 즉시 드러나는 문제이므로, 따로 시나리오를 짤 필요가 없음

 

2. 어떤 프로세스 외부 의존성을 직접 테스트해야 하는가?

프로세스 외부 의존성의 두 가지 유형

구분 설명 외부에서 관찰 가능 여부 예시
관리 의존성 구현 세부 사항 (애플리케이션 내부에서만 접근) ❌ 
내부 캐시, in-memory store
비관리 의존성 외부 시스템 (식별 가능한 부작용 발생) ✅ 
SMTP 서버, 메시지 버스

 

통합 테스트에서 실제 데이터베이스를 사용할 수 없으면 어떻게 할까?

사용할 수 없는 경우
상황 설명
관리 범위를 벗어남
외부 팀 또는 다른 시스템에서 관리하여 테스트 환경 설정이 어려움
실제 버전을 사용할 수 없음
테스트에 맞는 버전의 DB를 사용할 수 없음 (예: 라이센스 제한, 레거시 시스템)
테스트 환경에 배포 불가
테스트 환경에 데이터베이스 인스턴스를 설치하거나 구성할 수 없음

 

대처 방법
전략 설명
✔ 통합 테스트 생략
DB가 핵심 요소지만 사용할 수 없다면, 통합 테스트는 포기하고 단위 테스트에 집중
✔ 도메인 모델 단위 테스트 집중
비즈니스 로직의 핵심을 도메인 모델에서 검증함으로써 품질 확보
✔ QA 단계에서 외부 통신 집중 검증
QA 환경에서는 실제 DB 및 외부 시스템을 통합하여 검증 가능
🚫 목(Mock) 사용 피하기
❌ 쿼리 동작 테스트 불가
❌ 리팩터링 내성 약화: 메서드 호출 여부만 검증, 내부 구현 바뀌면 테스트가 깨지거나 무의미해짐

 

3. CRM 시스템

시나리오

회사 이메일을 일반 이메일로 바꾸기
  1. 이메일이 변경됨
  2. 유저 타입이 Employee → Customer로 바뀜
  3. 회사 직원 수가 1 줄어듦
  4. 메시지 버스로 "이메일 변경됨" 메시지 전송

 

데이터베이스와 메시지 버스 분류하기

외부 의존성을 관리 / 비관리로 나누어 목 여부를 결정하기
  • 애플리케이션 내부 데이터베이스: 관리 의존성 (구현 상세)
  • 메시지 버스: 비관리 의존성 (다른 시스템과의 통신)

 

엔드 투 엔드 테스트는 어떤가?

외부 클라이언트를 모방
  • 테스트 범위에 포함된 모든 프로세스 외부 의존성을 참조하는 배포된 버전의 애플리케이션을 테스트
  • 실제 API를 사용하여 시나리오를 수행 (목으로 대체 X)

 

통합 테스트

예제

더보기
public void Changing_email_from_non_corporate_to_corporate() {
    var db = new Database(ConnectionString);
    var user = CreateUser("user@gmail.com", UserType.Employee, db);
    CreateCompany("mycorp.com", 1, db);
    
    var messageBusMock = new Mock<IMessageBus>();
    var sut = new UserController(db, messageBusMock.Object);
    
    string result = sut.ChangeEmail(user.UserId, "new@gmail.com");
    
    Assert.Equal("OK", result);
    
    object[] userData = db.GetUserById(user.UserId);
    User userFromDb = UserFactory.Create(userData);
    Assert.Equal("new@gmail.com", userFromDb.Email);
    Assert.Equal(UserType.Customer, userFromDb.Type);
    
    object[] companyData = db.GetCompany();    
    Company companyFromDb = CompanyFactory.Create(companyData);
    Assert.Equal(0, companyFromDb.NumberOfEmployees);
    
    messageBusMock
    	.Verify(x => x.SendEmailChangedMessage(user.UserId, "new@gmail.com"), Times.Once);
}