Java 시니어 테스트 및 코드품질 기술면접

Java 시니어 (7년+) 테스트 · 코드품질 5문항 조회수 8 · 2026-09-17 (목) 07:41:43
1 테스트 격리 전략
Hard

Q. 대규모 레거시 시스템에서 외부 의존성(Redis, Kafka, Elasticsearch)이 많은 서비스의 통합 테스트를 작성해야 합니다. Testcontainers를 사용한 실제 컨테이너 기반 테스트, Embedded 서버 기반 테스트, Mock/Stub 기반 테스트의 장단점을 비교하고, 각 방식의 테스트 실행 시간과 CI/CD 파이프라인에서의 적합성을 설명해주세요. 또한 여러 테스트가 동시에 실행될 때 포트 충돌이나 데이터 격리 문제를 어떻게 해결하시겠습니까?

테스트 신뢰성, 실행 속도, 환경 일관성, 리소스 비용 측면에서 각 접근법의 트레이드오프를 고려해보세요.

A. 모범답안

Testcontainers는 실제 환경과 동일한 컨테이너를 사용해 가장 높은 신뢰성을 제공하지만 테스트당 5-10초의 컨테이너 시작 시간이 소요되므로 @Container의 static 선언으로 테스트 클래스당 한 번만 시작하거나, Singleton Containers 패턴으로 전체 테스트 스위트에서 재사용해야 합니다. Embedded 서버(H2, Embedded Redis)는 빠르지만 실제 프로덕션 환경과 동작이 다를 수 있어 SQL 방언이나 특정 기능 차이로 인한 false positive가 발생할 수 있습니다. Mock/Stub은 가장 빠르지만 통합 지점의 실제 동작을 검증하지 못하므로 계약 테스트(Contract Testing)나 Consumer-Driven Contract를 함께 사용해야 합니다. CI 환경에서는 Testcontainers를 병렬 실행할 때 Docker 리소스 제한과 네트워크 격리를 위해 각 컨테이너에 랜덤 포트를 할당하고, 테스트 데이터는 UUID 기반 네임스페이스로 격리하며, @DirtiesContext나 트랜잭션 롤백으로 테스트 간 상태를 격리합니다. 실무에서는 빠른 피드백을 위한 Mock 기반 단위 테스트 70%, 핵심 시나리오 검증을 위한 Testcontainers 기반 통합 테스트 20%, E2E 테스트 10%의 테스트 피라미드 구조를 유지하는 것이 효과적입니다.

핵심 포인트
  • • Testcontainers의 Singleton Containers 패턴으로 컨테이너 재사용
  • • Embedded 서버의 프로덕션 환경 불일치 위험
  • • Mock 기반 테스트와 Contract Testing 조합
  • • 랜덤 포트와 네임스페이스 기반 테스트 격리
  • • 테스트 피라미드 구조 유지
답변에 넣으면 좋은 키워드
Testcontainers Singleton Containers Embedded Server Contract Testing 테스트 격리 랜덤 포트
실무에서는

MSA 환경에서 여러 외부 시스템과 통합하는 서비스의 테스트 전략 수립 시 각 방식의 트레이드오프를 고려해 선택합니다.

Follow-up 질문

Testcontainers를 사용하는 테스트 스위트가 수백 개로 늘어나면서 CI 빌드 시간이 30분을 넘어갑니다. 테스트 실행 시간을 단축하기 위한 최적화 전략은 무엇인가요?

2 테스트 더블 설계
Hard

Q. 복잡한 비즈니스 로직을 가진 주문 처리 서비스를 테스트할 때, Mock, Stub, Spy, Fake의 차이점과 각각의 적절한 사용 시나리오를 설명해주세요. 특히 Mockito의 @Mock과 @Spy의 동작 방식 차이, 그리고 과도한 Mocking으로 인해 테스트가 구현 세부사항에 강하게 결합되는 문제(Over-mocking)를 어떻게 방지하시겠습니까? 또한 외부 API 호출을 테스트할 때 WireMock 같은 HTTP 모킹 라이브러리를 사용하는 것과 RestTemplate을 직접 Mock 하는 것의 장단점을 비교해주세요.

테스트의 목적(행위 검증 vs 상태 검증)과 테스트 유지보수성 관점에서 각 테스트 더블의 역할을 생각해보세요.

A. 모범답안

Mock은 메서드 호출 여부와 호출 횟수 등 행위를 검증하는 데 사용하고, Stub은 미리 정의된 응답을 반환해 상태 기반 테스트를 지원하며, Spy는 실제 객체를 부분적으로 모킹할 때 사용하고, Fake는 실제 구현의 경량 버전을 제공합니다. Mockito의 @Mock은 모든 메서드가 기본값(null, 0 등)을 반환하는 완전한 가짜 객체를 만들고, @Spy는 실제 객체를 감싸서 특정 메서드만 stubbing하고 나머지는 실제 로직이 실행되므로 레거시 코드 테스트나 부분 모킹이 필요할 때 유용합니다. Over-mocking을 방지하려면 public interface만 검증하고 내부 private 메서드 호출은 검증하지 않으며, verify() 사용을 최소화하고 상태 기반 검증을 우선하며, 테스트가 리팩토링에 취약하다면 Mock 대신 Fake 구현체를 만드는 것을 고려해야 합니다. WireMock은 HTTP 프로토콜 레벨에서 모킹하므로 실제 네트워크 호출 흐름을 검증할 수 있고 요청/응답 헤더, 상태 코드, 타임아웃 등을 정교하게 시뮬레이션할 수 있지만, RestTemplate을 직접 Mock하면 더 빠르고 간단하지만 HTTP 레이어의 실제 동작을 검증하지 못하므로 핵심 통합 테스트에는 WireMock을, 단순한 유닛 테스트에는 직접 Mock을 사용하는 것이 적절합니다.

핵심 포인트
  • • Mock(행위 검증), Stub(상태 검증), Spy(부분 모킹), Fake(경량 구현)
  • • @Mock과 @Spy의 동작 방식 차이
  • • Public interface 검증과 상태 기반 테스트로 Over-mocking 방지
  • • WireMock의 HTTP 레벨 검증과 직접 Mock의 단순성 트레이드오프
답변에 넣으면 좋은 키워드
Mock Stub Spy Fake Over-mocking WireMock 행위 검증 상태 검증
실무에서는

외부 결제 게이트웨이나 메시징 시스템과 통합하는 서비스의 테스트 작성 시 적절한 테스트 더블 선택이 중요합니다.

Follow-up 질문

테스트에서 ArgumentCaptor를 과도하게 사용하면 테스트가 구현에 강하게 결합되는데, ArgumentCaptor 사용을 최소화하면서도 복잡한 객체 전달을 검증하는 방법은 무엇인가요?

3 TDD 실천 전략
Medium

Q. TDD(Test-Driven Development)의 Red-Green-Refactor 사이클을 실무에 적용할 때, 복잡한 비즈니스 로직을 어떻게 작은 단위로 나누어 테스트를 먼저 작성하시겠습니까? 특히 외부 API 호출이나 데이터베이스 트랜잭션이 포함된 로직에서 TDD를 실천하는 구체적인 접근법과, TDD를 적용하기 어려운 레거시 코드에 점진적으로 테스트를 추가하는 전략을 설명해주세요. 또한 TDD가 설계 품질 향상에 기여하는 메커니즘과 한계점도 함께 설명해주세요.

테스트 가능한 설계를 위한 의존성 분리와 인터페이스 추상화, 그리고 점진적 개선 전략을 고려해보세요.

A. 모범답안

TDD를 실천하려면 먼저 비즈니스 요구사항을 작은 단위로 분해하고, 가장 간단한 케이스부터 시작해 점진적으로 복잡도를 높여가며 테스트를 추가합니다. 외부 의존성이 있는 로직은 인터페이스로 추상화하고 Mock이나 Stub으로 대체해 단위 테스트를 작성하며, 실제 통합은 별도의 통합 테스트로 검증합니다. 레거시 코드에는 Characterization Test(현재 동작을 있는 그대로 기록하는 테스트)를 먼저 작성해 안전망을 구축한 후, Strangler Fig 패턴으로 새로운 기능은 TDD로 작성하고 점진적으로 레거시를 대체합니다. TDD는 테스트 가능한 설계를 강제하므로 낮은 결합도와 높은 응집도를 자연스럽게 유도하고, 테스트가 먼저 작성되므로 API 사용자 관점에서 인터페이스를 설계하게 되며, 리팩토링 단계에서 지속적으로 코드 품질을 개선할 수 있습니다. 하지만 TDD만으로는 아키텍처 레벨의 설계 문제를 해결할 수 없고, 테스트 작성 시간이 초기 개발 속도를 늦출 수 있으며, UI나 복잡한 알고리즘처럼 테스트 작성이 어려운 영역도 존재합니다.

핵심 포인트
  • • 요구사항을 작은 단위로 분해하고 간단한 케이스부터 시작
  • • 외부 의존성은 인터페이스 추상화와 Mock으로 격리
  • • 레거시 코드에는 Characterization Test와 Strangler Fig 패턴 적용
  • • 테스트 가능한 설계 강제로 낮은 결합도 유도
  • • 아키텍처 설계와 초기 개발 속도는 TDD의 한계
답변에 넣으면 좋은 키워드
Red-Green-Refactor Characterization Test Strangler Fig 인터페이스 추상화 테스트 가능한 설계
실무에서는

새로운 기능 개발 시 TDD를 적용하면 초기에는 느리지만 장기적으로 버그가 줄고 리팩토링이 안전해집니다.

Follow-up 질문

Outside-In TDD와 Inside-Out TDD의 차이점과 각각의 장단점은 무엇이며, 실무에서 어떤 기준으로 선택하시겠습니까?

4 테스트 커버리지 전략
Medium

Q. 코드 커버리지 도구(JaCoCo, Cobertura)를 사용해 Line Coverage, Branch Coverage, Path Coverage를 측정할 때, 각 지표의 의미와 한계점을 설명하고, 높은 커버리지 수치에도 불구하고 실제로는 품질이 낮은 테스트가 작성될 수 있는 이유를 설명해주세요. 또한 팀에서 테스트 커버리지 목표를 설정할 때 어떤 기준을 사용해야 하며, 커버리지 게이트를 CI/CD 파이프라인에 적용할 때 고려해야 할 사항은 무엇인가요?

커버리지는 테스트 품질을 보장하지 않으며, 비즈니스 크리티컬한 영역에 집중하는 것이 중요합니다.

A. 모범답안

Line Coverage는 실행된 코드 라인의 비율을 측정하고, Branch Coverage는 if/else 같은 분기문의 모든 경로를 실행했는지 측정하며, Path Coverage는 가능한 모든 실행 경로를 측정하지만 경로 수가 기하급수적으로 증가해 실무에서는 측정하기 어렵습니다. 높은 커버리지에도 품질이 낮은 이유는 테스트가 코드를 실행하기만 하고 결과를 검증(assertion)하지 않거나, 예외 상황이나 경계값을 테스트하지 않거나, 테스트가 의미 있는 비즈니스 시나리오를 검증하지 못하기 때문입니다. 커버리지 목표는 프로젝트 특성에 따라 다르지만 일반적으로 핵심 비즈니스 로직은 80% 이상, 전체 평균은 70% 이상을 권장하며, Getter/Setter나 단순 위임 메서드는 제외하고, UI 레이어보다 도메인 레이어에 더 높은 기준을 적용합니다. CI/CD에서는 커버리지 감소를 차단하는 것은 유용하지만 절대값 기준은 초기 레거시 프로젝트에서 진입 장벽이 되므로 점진적으로 목표를 높이고, 신규 코드에만 높은 기준을 적용하며, 커버리지와 함께 Mutation Testing(PIT)으로 테스트의 실제 품질을 검증하는 것이 효과적입니다.

핵심 포인트
  • • Line, Branch, Path Coverage의 차이와 한계
  • • 커버리지 수치와 테스트 품질의 불일치
  • • 비즈니스 크리티컬 영역에 높은 기준 적용
  • • 점진적 목표 상향과 신규 코드 중심 관리
  • • Mutation Testing으로 테스트 품질 검증
답변에 넣으면 좋은 키워드
Line Coverage Branch Coverage Path Coverage JaCoCo Mutation Testing 커버리지 게이트
실무에서는

CI/CD 파이프라인에서 코드 커버리지 게이트를 설정해 품질 기준을 자동으로 강제하면서도 팀의 생산성을 저해하지 않는 균형을 찾아야 합니다.

Follow-up 질문

Mutation Testing 도구인 PIT를 사용해 테스트의 실제 결함 탐지 능력을 측정할 때, Mutation Score가 낮게 나오는 주요 원인과 개선 방법은 무엇인가요?

5 리팩토링과 코드리뷰
Hard

Q. 대규모 레거시 시스템에서 수천 줄의 God Class를 리팩토링해야 합니다. Extract Method, Extract Class, Move Method 같은 리팩토링 기법을 안전하게 적용하기 위한 전제조건과 단계별 전략을 설명하고, 리팩토링 중 기능 변경이 없음을 보장하는 방법을 설명해주세요. 또한 코드리뷰에서 리팩토링 PR을 검토할 때 어떤 체크리스트를 사용하며, 리팩토링과 기능 개발을 동시에 진행하는 PR을 어떻게 다루시겠습니까? 마틴 파울러의 리팩토링 원칙 중 실무에서 가장 중요하다고 생각하는 것과 그 이유도 함께 설명해주세요.

테스트 커버리지 확보가 선행되어야 하며, 작은 단위로 나누어 점진적으로 개선하는 것이 안전합니다.

A. 모범답안

God Class 리팩토링 전에는 먼저 Characterization Test로 현재 동작을 보호하는 테스트를 작성하고, 최소 70% 이상의 커버리지를 확보한 후 시작해야 하며, IDE의 자동 리팩토링 기능을 최대한 활용해 수동 실수를 방지합니다. Extract Method로 긴 메서드를 작은 단위로 나누고, Extract Class로 관련 메서드와 필드를 새로운 클래스로 분리하며, Move Method로 적절한 클래스로 책임을 이동시키는데, 각 단계마다 테스트를 실행해 회귀 버그를 즉시 발견합니다. 리팩토링 PR 검토 시에는 기능 변경이 없는지, 테스트가 모두 통과하는지, 순환 의존성이 생기지 않았는지, 새로운 클래스의 응집도가 높은지, 변경 범위가 적절한지를 확인하며, 리팩토링과 기능 개발이 섞인 PR은 반드시 분리를 요청해야 합니다. 리팩토링은 별도 PR로 먼저 머지하고 기능 개발은 그 위에 쌓는 것이 원칙이며, 부득이한 경우 커밋을 명확히 분리해 리뷰어가 구분할 수 있게 합니다. 마틴 파울러의 원칙 중 '리팩토링과 기능 추가를 분리하라'가 가장 중요한데, 두 가지를 동시에 하면 문제 발생 시 원인을 찾기 어렵고 코드리뷰가 복잡해지며 롤백도 어려워지기 때문입니다.

핵심 포인트
  • • Characterization Test로 안전망 확보 후 리팩토링 시작
  • • Extract Method, Extract Class, Move Method 단계별 적용
  • • 각 단계마다 테스트 실행으로 회귀 버그 즉시 발견
  • • 리팩토링과 기능 개발 PR 분리 원칙
  • • 리팩토링과 기능 추가 분리가 가장 중요한 원칙
답변에 넣으면 좋은 키워드
God Class Extract Method Extract Class Characterization Test 리팩토링 PR 회귀 테스트
실무에서는

레거시 시스템을 점진적으로 개선할 때 안전한 리팩토링 프로세스와 명확한 코드리뷰 기준이 기술 부채 해소의 핵심입니다.

Follow-up 질문

IntelliJ나 Eclipse의 자동 리팩토링 기능을 사용할 때도 주의해야 할 함정이 있습니다. 자동 리팩토링이 안전하지 않은 경우는 언제이며, 어떻게 검증하시겠습니까?

댓글 0

로그인 후 댓글을 작성할 수 있습니다.

아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!