위클리페이퍼08: 테스트 주도 개발론(TDD)과 서비스 안정성

Q1. 애플리케이션의 각 계층에서 수행되는 입력값 검증의 범위와 책임을 어떻게 나눌 것인지에 대해 설명해주세요. 특히 중복 검증을 피하면서도 안정성을 확보하는 방안과, 이와 관련된 트레이드오프에 대해 설명해주세요.

Q1-1. 답변

애플리케이션 계층별 검증의 범위와 책임

  • 클라이언트 계층
    • 범위와 책임
      • HTML5의 required 속성이나 JavaScript를 활용하여 형식이나 필수값 누락을 1차적으로 검사
    • 목적
      • 서버로 불필요한 요청이 가는 것을 막음
  • API 계층 (Controller)
    • 범위와 책임
      • 클라이언트로 전달받은 요청 파라미터에 대해 형식(포맷) 및 필수값 포함 여부 등 기본적인 유효성을 주로 검증
      • @Valid@Validated 사용하여 DTO에서 검증을 수행함
    • 목적
      • 잘못된 입력값이 내부로 유입되는 것을 진입 시점에서 차단
      • 공통된 에러 응답을 제공
  • Service 계층
    • 범위와 책임
      • 복잡한 비즈니스 규칙과 도메인 객체 간의 관계를 검증
      • 트랜잭션 관리
    • 목적
      • Controller와 역할을 분리하여 도메인 중심 비즈니스 유효성 검사 수행
  • Domain 계층
    • 범위와 책임
      • 스스로 자신의 데이터(상태)가 설정한 규칙을 어기지 않는지 검증
      • 예시: 생성자 내부에 if (amount < 0) 예외_발생 같은 로직을 넣는 것
    • 목적
      • 규칙 위반 시 즉시 예외를 발생시켜 시스템 일관성과 안정성을 유지

중복 검증 회피 및 안정성 확보 방안과 트레이드오프

  • 방안
    • 계층별 책임의 명확한 분리
      • 한 곳에 모든 것을 검증하거나 검증 로직이 분산되면 유지보수가 어려워지고 일부 로직을 빠뜨릴 수 있다.
      • 이를 방지하기 위해, Controller에서는 입력값에 대한 유효성, Service에서는 비즈니스 규칙에 대한 유효성으로 검증 시점을 나누어 적용
    • 상황별 검증 그룹의 활용
      • 동일한 객체라도 상황에 따라 필수적인 검증 규칙이 다를 수 있다. 이 때는 Controller나 Service 계층에서 @Validated 검증 그룹 기능을 활용
  • 트레이드오프
    • @Valid
      • 장점
        • 객체 내부에 또 다른 객체나 컬렉션을 품고 있는 복합 객체일 때 하위 요소들까지 재귀적으로 검증이 전파 가능해서 안정성이 크게 향상됨
      • 단점
        • 객체의 중첩 구조가 깊고 복잡할 경우 애플리케이션 성능에 악영향을 줌
    • @Pattern 같은 정규표현식
      • 장점
        • 특정 포맷을 검증할 때 코드 작성이 간결함
      • 단점
        • 패턴이 복잡할 경우 백트래킹이 다수 발생하여 성능 저하

Q2. 테스트에서 사용되는 Mockito의 Mock, Stub, Spy 개념을 각각 설명하고, 어떤 상황에서 어떤 방식을 선택해야 하는지 구체적인 예시와 함께 설명하세요.

Q2-1. 답변

Mockito

Java에서 널리 사용되는 Mocking 라이브러리로, Mock 객체를 만들어주는 도구이다.

Stub (스텁)

테스트 대상 객체가 의존하는 외부 컴포넌트의 동작을 미리 하드코딩하여, 요청에 대해 항상 고정된 응답(결과값)만을 반환하는 단순한 객체

  • 선택 상황
    • 외부 시스템의 복잡한 로직을 무시하고, 단순한 조건이나 특정 고정된 결과값이 필요한 테스트 환경을 구축할 때 선택
  • 예시
    • getBalance(userId) 메서드 호출 시 무조건 10,000원을 반환하도록 when(...).thenReturn(...)(given(…).willReturn(…)) 설정하면 비즈니스 로직만 집중해서 테스트 가능

Mock

실제 객체처럼 동작하도록 조작하고, 테스트 중에 특정 메서드가 호출되었는지, 몇 번 호출되었는지, 어떤 파라미터가 호출되었는지 등의 행위(Behavior) 자체를 검증하는 가짜 객체

  • 선택 상황
    • 값을 반환하는 것을 넘어서 외부 시스템과의 연동 등 객체 간의 상호작용이 올바르게 일어났는지 검증할 때 선택
  • 예시
    • 회원가입 로직에서 회원가입이 완료되면 가입 환영 메일을 보내는 EmailService가 있을 때, verify()메서드를 이용해 sendEmail()메서드가 정확한 수신자와 제목으로 1회 호출되었는지 검증

Spy

가짜 객체를 완전히 대체하는 Mock과 달리, 실제 객체를 감싸(Wrapper) 기본적으로 실제 로직대로 동작하게 함녀서, 특정 일부 메서드만 가짜(Mocking) 동작으로 덮어씌워 대체하거나 호출 기록을 감시할 수 있는 객체

  • 선택 상황
    • 객체의 모든 동작을 가짜로 만들지 않고, 대부분의 코드는 실제 시스템 로직을 그대로 사용하지만 특정 외부 연동 메서드 등 일부만 대체하여 테스트할 때 선택
  • 예시
    • 외부 결제 게이트웨이와 연동하는 시스템을 테스트할 때, 다른 비즈니스 로직은 그대로 사용하고 외부 게이트웨이와 통신하는 메서드만 가짜 응답을 반환하도록 조작

Leave a comment