자료구조/알고리즘 미드레벨 테스트·코드품질 면접
새 면접Q. 이진 탐색 트리(BST)의 삽입, 삭제, 검색 기능을 구현했습니다. 이 자료구조에 대한 단위 테스트를 작성할 때 어떤 테스트 케이스들을 반드시 포함해야 하며, 엣지 케이스는 무엇이 있을까요? 테스트 커버리지를 높이기 위한 전략과 함께 설명해주세요.
정상 케이스뿐만 아니라 트리의 균형이 깨지는 경우, 빈 트리, 단일 노드 등 경계 조건을 고려해보세요.
BST 단위 테스트는 크게 정상 케이스, 경계 케이스, 예외 케이스로 나눕니다. 정상 케이스는 균형 잡힌 트리에서의 삽입/삭제/검색을 테스트하고, 경계 케이스는 빈 트리, 단일 노드, 완전히 편향된 트리(연결 리스트 형태)를 다룹니다. 삭제 시에는 자식이 없는 노드, 한 개인 노드, 두 개인 노드 각각의 경우를 테스트해야 합니다. 중복 값 삽입, null 값 처리, 존재하지 않는 값 삭제 등의 예외 상황도 포함해야 합니다. 테스트 커버리지를 높이기 위해 Property-based Testing을 활용하여 랜덤한 삽입/삭제 시퀀스에서도 BST의 불변 조건(왼쪽 < 부모 < 오른쪽)이 유지되는지 검증합니다. 각 테스트는 Given-When-Then 패턴으로 작성하여 가독성을 높이고, 테스트 픽스처를 활용해 중복 코드를 줄입니다.
- • 정상/경계/예외 케이스를 모두 포함하는 포괄적인 테스트 설계
- • 삭제 연산의 세 가지 경우(자식 0개, 1개, 2개)를 각각 테스트
- • 편향된 트리 등 성능 저하 케이스 검증
- • Property-based Testing으로 불변 조건 검증
검색 기능이나 인메모리 인덱스를 구현할 때 트리 자료구조의 정확성을 보장하기 위해 사용됩니다.
BST가 AVL 트리나 Red-Black 트리로 변경된다면 테스트 전략을 어떻게 수정하시겠습니까?
Q. LRU(Least Recently Used) 캐시를 TDD 방식으로 구현하려고 합니다. 어떤 순서로 테스트를 작성하고 기능을 구현해 나가시겠습니까? Red-Green-Refactor 사이클을 고려하여 최소 5단계 이상의 TDD 프로세스를 설명해주세요.
가장 간단한 기능부터 시작해서 점진적으로 복잡한 기능을 추가하는 순서를 생각해보세요.
첫 번째로 빈 캐시를 생성하고 get 시 null을 반환하는 테스트를 작성합니다(Red). 간단한 HashMap만으로 통과시킵니다(Green). 두 번째로 단일 항목 put과 get이 동작하는 테스트를 작성하고, 기본 저장 로직을 구현합니다. 세 번째로 capacity를 초과하지 않는 범위에서 여러 항목을 저장/조회하는 테스트를 추가합니다. 네 번째로 capacity를 초과할 때 가장 오래된 항목이 제거되는 테스트를 작성하고, 이때 LinkedHashMap이나 HashMap+DoublyLinkedList 조합으로 리팩터링합니다(Refactor). 다섯 번째로 get 호출이 접근 순서를 업데이트하는 테스트를 추가하여 LRU 정책을 완성합니다. 여섯 번째로 동일 키에 대한 put이 업데이트로 동작하고 순서도 갱신되는지 테스트합니다. 각 단계마다 테스트가 실패하는 것을 확인한 후(Red) 최소한의 코드로 통과시키고(Green), 중복 제거와 구조 개선을 진행합니다(Refactor).
- • 가장 간단한 케이스부터 시작하여 점진적으로 복잡도 증가
- • 각 단계마다 Red-Green-Refactor 사이클 준수
- • capacity 초과와 접근 순서 갱신이 핵심 기능
- • 리팩터링 단계에서 적절한 자료구조로 전환
API 응답 캐싱이나 세션 관리 등에서 LRU 캐시를 구현할 때 TDD로 안정성을 확보합니다.
TDD로 개발할 때 성능 테스트는 어느 시점에 어떻게 추가하시겠습니까?
Q. 레거시 코드에서 정렬 로직이 여러 곳에 중복되어 있고, 각각 다른 정렬 알고리즘(버블 정렬, 삽입 정렬, 퀵 정렬)을 사용하고 있습니다. 이를 Strategy 패턴으로 리팩토링하려고 할 때, 기존 동작을 보장하면서 안전하게 리팩토링하는 과정을 단계별로 설명해주세요. 특히 테스트 코드 작성 시점과 리팩토링 검증 방법을 포함해주세요.
리팩토링 전 현재 동작을 보호하는 테스트를 먼저 작성하는 것이 핵심입니다.
첫 번째로 기존 코드의 동작을 보호하기 위해 Characterization Test(특성 테스트)를 작성합니다. 각 정렬 로직이 사용되는 모든 지점에서 입력과 출력을 캡처하여 회귀 테스트를 만듭니다. 두 번째로 각 정렬 알고리즘을 별도 메서드로 추출(Extract Method)하고, 테스트가 여전히 통과하는지 확인합니다. 세 번째로 SortStrategy 인터페이스를 정의하고 BubbleSort, InsertionSort, QuickSort 구현체를 생성하되, 기존 메서드는 그대로 유지합니다. 네 번째로 한 곳씩 점진적으로 Strategy 패턴을 적용하고, 각 변경 후 테스트를 실행합니다. Feature Flag를 사용하여 새 구현과 기존 구현을 동시에 실행하고 결과를 비교하는 Shadow Testing을 수행합니다. 다섯 번째로 모든 지점이 전환되고 충분한 검증이 끝나면 레거시 코드를 제거하고, 새로운 구조에 맞는 단위 테스트로 교체합니다. 전 과정에서 작은 단위로 커밋하고, 각 커밋마다 테스트가 통과하는 상태를 유지합니다.
- • 리팩토링 전 Characterization Test로 기존 동작 보호
- • 점진적 전환과 각 단계마다 테스트 실행
- • Shadow Testing으로 새 구현 검증
- • Feature Flag를 활용한 안전한 전환
레거시 시스템을 현대적인 디자인 패턴으로 전환할 때 서비스 중단 없이 안전하게 개선하는 데 사용됩니다.
리팩토링 중에 버그를 발견했다면 어떻게 처리하시겠습니까?
Q. Bloom Filter를 사용하는 중복 체크 시스템의 통합 테스트를 작성하려고 합니다. 이 시스템은 Redis에 Bloom Filter 데이터를 저장하고, 외부 API로부터 데이터를 받아 처리합니다. 통합 테스트 환경 구성, 테스트 데이터 준비, 테스트 격리 전략을 어떻게 설계하시겠습니까?
실제 Redis 대신 테스트용 인프라를 사용하고, 각 테스트가 독립적으로 실행되도록 하는 방법을 고려하세요.
통합 테스트 환경은 Testcontainers를 사용하여 실제 Redis 컨테이너를 테스트마다 새로 띄우는 방식으로 구성합니다. 외부 API는 WireMock이나 MockServer로 스텁을 만들어 네트워크 의존성을 제거하고 응답을 제어 가능하게 만듭니다. 테스트 데이터는 Builder 패턴이나 Fixture Factory를 사용하여 일관되게 생성하고, 테스트 시나리오별로 필요한 초기 상태를 명확히 정의합니다. 각 테스트는 독립적으로 실행되어야 하므로, @BeforeEach에서 Redis를 초기화하거나 테스트마다 고유한 키 네임스페이스를 사용합니다. Bloom Filter의 특성상 False Positive가 발생할 수 있으므로, 통계적으로 유의미한 데이터 세트로 오류율을 검증하는 테스트도 포함합니다. 테스트 실행 시간을 줄이기 위해 공통 셋업이 필요한 테스트들은 @Nested 클래스로 그룹화하여 컨텍스트를 공유할 수 있습니다.
- • Testcontainers로 실제 Redis 환경 구성
- • WireMock으로 외부 API 의존성 제거
- • 테스트 격리를 위한 초기화 전략
- • Bloom Filter의 확률적 특성을 고려한 검증
대용량 데이터에서 중복 체크가 필요한 크롤러나 스팸 필터 시스템에서 Bloom Filter와 통합 테스트가 함께 사용됩니다.
통합 테스트가 너무 느려진다면 어떤 최적화 전략을 사용하시겠습니까?
Q. 동료가 작성한 PR에서 Segment Tree를 사용한 구간 합 쿼리 구현을 리뷰하고 있습니다. 코드는 동작하지만 가독성이 떨어지고, 재귀 깊이 제한 문제가 발생할 수 있으며, 테스트가 부족합니다. 코드 리뷰 시 어떤 피드백을 어떤 우선순위로 제공하시겠습니까? 피드백을 전달하는 방식과 함께 설명해주세요.
치명적인 버그, 성능 문제, 코드 품질 순으로 우선순위를 두고, 건설적인 피드백 방식을 고려하세요.
첫 번째 우선순위는 재귀 깊이 제한으로 인한 런타임 에러 가능성입니다. 'P0: 입력 크기가 큰 경우 StackOverflowError가 발생할 수 있습니다. 재귀 대신 반복문 구현을 고려해주세요'라고 명확히 표시합니다. 두 번째로 엣지 케이스 테스트 부족을 지적합니다. '빈 배열, 단일 요소, 구간이 전체 범위인 경우 등의 테스트를 추가하면 좋겠습니다. 예시 테스트 코드를 첨부해드립니다'처럼 구체적인 해결 방안을 제시합니다. 세 번째로 가독성 개선을 요청합니다. '변수명 l, r 대신 leftBound, rightBound처럼 의미를 명확히 하면 유지보수가 쉬워집니다'처럼 제안 형태로 전달합니다. 네 번째로 시간복잡도와 공간복잡도를 문서화하도록 요청합니다. 모든 피드백은 'Nit', 'Suggestion', 'P0/P1/P2' 같은 라벨로 중요도를 표시하고, 비판보다는 '~하면 더 좋을 것 같습니다' 형태의 건설적 표현을 사용합니다. 코드의 좋은 점(알고리즘 선택이 적절함)도 함께 언급하여 균형잡힌 리뷰를 합니다.
- • 치명적 버그, 성능, 품질 순으로 우선순위 설정
- • 구체적인 해결 방안과 예시 코드 제공
- • 중요도 라벨링으로 명확한 커뮤니케이션
- • 긍정적 피드백과 건설적 제안의 균형
팀의 코드 품질을 유지하고 지식을 공유하며, 버그를 사전에 발견하는 핵심 프로세스입니다.
리뷰어와 작성자 간 의견이 충돌할 때 어떻게 해결하시겠습니까?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!