Vue.js 시니어 테스트 · 코드품질 기술면접
새 면접Q. Vue 3 Composition API로 작성된 복잡한 Composable 함수에서 watchEffect와 computed가 혼합되어 있고 외부 API 호출도 포함되어 있습니다. 이러한 Composable의 테스트 격리성을 보장하면서도 실제 반응성 동작을 검증하는 테스트 전략을 설명해주세요. 특히 Vue의 반응성 시스템을 어디까지 모킹하고 어디까지 실제로 실행할지, 그리고 비동기 반응성 효과를 안정적으로 테스트하는 방법을 포함하여 설명해주세요.
반응성 시스템은 실제로 실행하되 외부 의존성만 모킹하는 방식과, flushPromises나 nextTick을 활용한 비동기 처리 검증을 고려해보세요.
Composable 테스트는 Vue의 반응성 시스템을 실제로 실행하면서 외부 의존성만 모킹하는 것이 가장 효과적입니다. createApp이나 mount 없이 setup 컨텍스트에서 직접 Composable을 호출하고, ref/computed의 실제 동작을 검증합니다. 외부 API는 vi.mock이나 MSW로 모킹하고, watchEffect의 실행은 await nextTick()이나 await flushPromises()로 대기한 후 검증합니다. 복잡한 반응성 체인은 effectScope를 사용해 명시적으로 생성하고 테스트 종료 시 scope.stop()으로 정리하여 메모리 누수를 방지합니다. 타이머가 포함된 경우 vi.useFakeTimers()를 사용하고, 여러 비동기 효과가 연쇄적으로 발생하는 경우 각 단계마다 nextTick을 명시적으로 호출하여 안정적인 검증을 수행합니다.
- • 반응성 시스템은 실제 실행, 외부 의존성만 모킹
- • nextTick과 flushPromises로 비동기 반응성 효과 검증
- • effectScope를 활용한 명시적 생명주기 관리
- • 타이머 모킹과 연쇄 비동기 효과의 단계별 검증
복잡한 데이터 페칭 로직이나 폼 검증 로직을 Composable로 추출했을 때 독립적으로 테스트할 수 있어야 합니다.
Composable이 다른 Composable을 의존하는 경우 테스트 더블을 어떻게 주입하시겠습니까?
Q. Vue Router와 Pinia를 사용하는 대시보드 애플리케이션에서 사용자 플로우 전체를 검증하는 통합 테스트를 작성한다고 가정합니다. 로그인 → 데이터 로딩 → 필터 적용 → 상세 페이지 이동의 플로우를 테스트할 때, 라우터 네비게이션과 스토어 상태 변경, 그리고 비동기 API 호출이 모두 얽혀있습니다. 이러한 복잡한 통합 테스트에서 테스트의 신뢰성과 실행 속도를 모두 확보하기 위한 전략을 설명해주세요.
실제 라우터와 스토어 인스턴스를 사용하되 API 레이어만 모킹하고, Testing Library의 user-event를 활용한 사용자 시뮬레이션을 고려해보세요.
통합 테스트는 실제 라우터와 스토어 인스턴스를 생성하여 사용하되 API 레이어만 MSW나 axios mock으로 대체합니다. createRouter와 createPinia를 테스트마다 새로 생성하여 격리성을 보장하고, router.isReady()를 await하여 초기 네비게이션 완료를 확인합니다. Testing Library의 waitFor와 findByRole을 사용해 비동기 렌더링을 검증하고, userEvent.click 같은 사용자 이벤트 시뮬레이션으로 실제 사용자 플로우를 재현합니다. 각 네비게이션 단계마다 router.currentRoute.value로 현재 라우트를 검증하고, 스토어 상태는 직접 접근하여 확인합니다. 테스트 속도를 위해 트랜지션은 비활성화하고, 중요하지 않은 컴포넌트는 스텁으로 대체하되 테스트 대상 플로우의 핵심 컴포넌트는 실제로 렌더링합니다.
- • 실제 라우터/스토어 인스턴스 사용, API만 모킹
- • router.isReady()와 waitFor로 비동기 완료 대기
- • userEvent로 실제 사용자 인터랙션 시뮬레이션
- • 트랜지션 비활성화와 선택적 컴포넌트 스텁으로 속도 최적화
결제 플로우나 다단계 폼 작성 같은 중요한 사용자 여정을 E2E 없이도 빠르게 검증할 수 있습니다.
Navigation Guard에서 인증 실패로 리다이렉트되는 시나리오는 어떻게 테스트하시겠습니까?
Q. Vue 컴포넌트를 TDD 방식으로 개발할 때 테스트를 먼저 작성하는 접근법의 실질적인 어려움과 이를 극복하는 전략을 설명해주세요. 특히 Vue의 템플릿 기반 렌더링과 반응성 시스템으로 인해 발생하는 TDD의 장애물과, 컴포넌트의 어떤 측면을 먼저 테스트로 정의해야 효과적인지 설명해주세요.
컴포넌트의 공개 인터페이스(props, events, slots)를 먼저 테스트로 정의하고, 내부 구현은 나중에 추가하는 접근을 고려해보세요.
Vue 컴포넌트 TDD는 컴포넌트의 공개 API(props, emits, slots, expose)를 먼저 테스트로 정의하는 것이 효과적입니다. props를 전달했을 때 기대하는 렌더링 결과와 사용자 인터랙션 시 발생해야 하는 이벤트를 먼저 테스트로 작성합니다. 초기에는 최소한의 구현으로 테스트를 통과시키고, 리팩토링 단계에서 Composition API의 Composable로 로직을 추출하거나 computed를 도입합니다. 템플릿은 렌더링 결과로 검증하되 구체적인 DOM 구조보다는 역할(role)과 텍스트 내용으로 검증하여 템플릿 변경에 유연하게 대응합니다. 복잡한 반응성 로직은 Composable로 분리하여 독립적으로 TDD를 적용하고, 컴포넌트는 얇은 프레젠테이션 레이어로 유지합니다.
- • 공개 API(props, emits, slots)를 테스트로 먼저 정의
- • 역할과 텍스트 기반 검증으로 템플릿 변경에 유연하게 대응
- • 복잡한 로직은 Composable로 분리하여 독립적으로 TDD 적용
- • Red-Green-Refactor 사이클에서 리팩토링 단계에서 반응성 최적화
재사용 가능한 UI 컴포넌트 라이브러리를 개발할 때 명확한 계약을 먼저 정의하고 구현할 수 있습니다.
비동기 데이터 로딩이 포함된 컴포넌트를 TDD로 개발할 때 테스트 작성 순서는 어떻게 되나요?
Q. Vue 3 프로젝트의 코드 리뷰에서 반응성 관련 안티패턴을 발견했을 때 어떤 항목들을 중점적으로 검토하시나요? 특히 성능 문제나 예상치 못한 동작을 유발할 수 있는 반응성 사용 패턴과, 이를 개선하기 위한 구체적인 리뷰 코멘트 예시를 제시해주세요.
불필요한 깊은 반응성, ref 언래핑 오해, watch의 즉시 실행 부작용 등 흔한 실수 패턴을 고려해보세요.
코드 리뷰에서는 먼저 reactive로 대용량 배열이나 깊은 중첩 객체를 감싸는 경우 shallowReactive나 shallowRef 사용을 제안합니다. computed 내부에서 부수효과를 발생시키거나 비동기 작업을 수행하는 경우 watchEffect로 분리하도록 권장합니다. watch의 immediate 옵션 사용 시 초기 렌더링 전에 실행되어 DOM 접근 오류가 발생할 수 있음을 지적하고 onMounted와 함께 사용하도록 안내합니다. ref를 구조분해 할당하여 반응성을 잃는 경우 toRefs나 toRef 사용을 제안하고, template에서 .value를 사용하는 경우 ref 언래핑 규칙을 설명합니다. 또한 setup에서 반환하지 않은 ref가 템플릿에서 사용되거나, props를 직접 수정하는 경우 즉시 수정을 요청합니다.
- • 대용량 데이터는 shallowReactive/shallowRef 사용 제안
- • computed 내부 부수효과는 watchEffect로 분리
- • watch immediate 옵션의 DOM 접근 오류 주의
- • 구조분해 할당 시 toRefs 사용, props 직접 수정 금지
대규모 팀에서 일관된 반응성 사용 패턴을 유지하고 잠재적 버그를 사전에 방지할 수 있습니다.
onBeforeUnmount에서 정리하지 않은 타이머나 이벤트 리스너를 발견했을 때 어떻게 코멘트하시나요?
Q. Options API로 작성된 1000줄 이상의 레거시 컴포넌트를 Composition API로 리팩토링하는 프로젝트를 진행합니다. 기존 테스트를 깨뜨리지 않으면서 점진적으로 리팩토링하는 전략과, 리팩토링 과정에서 코드 품질을 개선하기 위해 적용할 수 있는 구체적인 패턴을 설명해주세요.
믹스인과 computed, watch를 Composable로 추출하고, 기능 단위로 분리하면서 테스트를 유지하는 접근을 고려해보세요.
먼저 기존 컴포넌트의 통합 테스트를 작성하여 리팩토링 전후 동작이 동일함을 검증할 안전망을 구축합니다. mixins를 우선적으로 Composable로 추출하고, 각 Composable에 대한 단위 테스트를 작성합니다. data와 methods는 기능별로 그룹화하여 여러 Composable로 분리하되, 한 번에 하나씩 추출하여 매번 테스트를 실행합니다. computed와 watch는 관련된 상태와 함께 Composable로 이동시키고, 복잡한 로직은 순수 함수로 분리하여 테스트 가능성을 높입니다. 컴포넌트는 script setup으로 전환하되, 초기에는 기존 구조를 유지하고 점진적으로 개선합니다. props와 emits는 TypeScript로 타입을 명시하고, 큰 템플릿은 작은 컴포넌트로 분리하여 책임을 나눕니다. 리팩토링 완료 후 불필요한 반응성을 제거하고 shallowRef 같은 최적화를 적용합니다.
- • 통합 테스트로 안전망 구축 후 점진적 리팩토링
- • mixins와 기능별 로직을 Composable로 단계적 추출
- • 복잡한 로직은 순수 함수로 분리하여 테스트 가능성 향상
- • TypeScript 타입 추가와 컴포넌트 분리로 유지보수성 개선
레거시 프로젝트를 현대적인 패턴으로 전환하면서도 비즈니스 연속성을 유지해야 할 때 필수적입니다.
리팩토링 중 발견한 잠재적 버그는 어떻게 처리하시겠습니까?
Q. Vue 프로젝트에서 테스트 커버리지 80% 이상을 달성했지만 여전히 프로덕션 버그가 발생합니다. 단순 커버리지 수치를 넘어서 실질적인 품질을 보장하는 테스트 전략을 수립한다면 어떤 메트릭과 접근법을 사용하시겠습니까? 특히 Vue 컴포넌트의 특성을 고려한 테스트 품질 평가 방법을 설명해주세요.
분기 커버리지, 엣지 케이스 테스트, 사용자 플로우 커버리지, 그리고 mutation testing 같은 고급 메트릭을 고려해보세요.
단순 라인 커버리지보다 분기 커버리지(branch coverage)를 측정하여 모든 조건문의 true/false 경로를 검증합니다. 각 컴포넌트의 props 조합 중 엣지 케이스(빈 배열, null, undefined, 극단값)를 명시적으로 테스트하는 체크리스트를 만들고, 에러 바운더리와 로딩/에러 상태도 반드시 커버합니다. Mutation Testing(Stryker 같은 도구)을 도입하여 테스트가 실제로 코드 변경을 감지하는지 검증합니다. 사용자 플로우 기반 커버리지를 측정하여 중요 비즈니스 시나리오가 모두 테스트되는지 확인하고, Cypress나 Playwright로 크리티컬 패스를 E2E 테스트합니다. 코드 리뷰에서 새로운 코드가 추가될 때 테스트도 함께 작성되었는지, 특히 조건부 렌더링과 이벤트 핸들러가 모두 검증되었는지 확인합니다.
- • 분기 커버리지와 엣지 케이스 명시적 테스트
- • Mutation Testing으로 테스트 품질 자체를 검증
- • 사용자 플로우 기반 커버리지와 크리티컬 패스 E2E 테스트
- • 코드 리뷰에서 테스트 작성 여부와 품질 검증
높은 커버리지 수치에도 불구하고 실제 버그를 놓치는 경우 테스트 전략을 재검토해야 합니다.
레거시 코드의 커버리지를 점진적으로 높이는 전략은 무엇인가요?
Q. Vue 컴포넌트 테스트에서 자식 컴포넌트를 스텁(stub)으로 대체할지 실제로 렌더링할지 결정하는 기준을 설명해주세요. 특히 써드파티 UI 라이브러리 컴포넌트, 비즈니스 로직을 포함한 자식 컴포넌트, 그리고 단순 프레젠테이션 컴포넌트를 각각 어떻게 처리하는 것이 적절한지 설명해주세요.
테스트의 목적(단위 vs 통합), 테스트 속도, 그리고 테스트 실패 시 원인 파악의 용이성을 기준으로 판단해보세요.
써드파티 UI 라이브러리 컴포넌트(Vuetify, Element Plus 등)는 스텁으로 대체하여 외부 의존성을 제거하고 테스트 속도를 높입니다. 이들은 자체 테스트를 가지고 있으며, 우리가 검증할 대상은 올바른 props를 전달하는지입니다. 복잡한 비즈니스 로직을 포함한 자식 컴포넌트는 단위 테스트에서는 스텁으로 대체하고, 통합 테스트에서는 실제로 렌더링하여 상호작용을 검증합니다. 단순 프레젠테이션 컴포넌트(버튼, 아이콘 등)는 실제로 렌더링하여 전체적인 UI 동작을 확인합니다. 스텁 사용 시 emitted 이벤트와 전달된 props를 검증하고, shallow mount보다는 mount with stubs를 사용하여 필요한 부분만 선택적으로 스텁합니다. 테스트 실패 시 원인을 빠르게 파악하려면 테스트 범위를 좁게 유지하는 것이 중요하므로, 복잡한 자식 컴포넌트는 스텁으로 대체합니다.
- • 써드파티 UI 라이브러리는 스텁으로 대체
- • 비즈니스 로직 포함 컴포넌트는 단위/통합 테스트에서 다르게 처리
- • 단순 프레젠테이션 컴포넌트는 실제 렌더링
- • 테스트 범위와 실패 원인 파악 용이성을 기준으로 판단
대규모 컴포넌트 트리에서 테스트 속도와 안정성을 모두 확보하기 위해 스텁 전략이 필요합니다.
Portal이나 Teleport를 사용하는 컴포넌트는 어떻게 테스트하시나요?
Q. Vue 애플리케이션의 E2E 테스트를 Cypress나 Playwright로 작성할 때, 테스트의 안정성과 유지보수성을 높이기 위한 베스트 프랙티스를 설명해주세요. 특히 SPA의 비동기 렌더링과 라우팅, 그리고 외부 API 의존성을 다루는 전략을 포함하여 설명해주세요.
data-testid 사용, API 인터셉트, 명시적 대기, Page Object Pattern 같은 패턴을 고려해보세요.
E2E 테스트는 data-testid 속성을 사용하여 CSS 클래스나 태그에 의존하지 않는 안정적인 셀렉터를 구축합니다. Cypress의 cy.intercept()나 Playwright의 route를 사용하여 외부 API를 모킹하고 일관된 응답을 제공하여 테스트 안정성을 높입니다. SPA의 비동기 렌더링은 명시적 대기(cy.get().should('be.visible'))를 사용하고, 임의의 타임아웃은 피합니다. Page Object Pattern을 적용하여 페이지별 인터랙션 로직을 캡슐화하고, 테스트 코드의 중복을 제거합니다. 라우터 네비게이션은 URL 변경과 함께 특정 요소의 렌더링을 함께 검증하여 페이지 전환이 완료되었음을 확인합니다. 테스트 데이터는 beforeEach에서 초기화하고, 테스트 간 격리를 보장하며, 중요한 사용자 플로우만 E2E로 테스트하고 나머지는 통합 테스트로 커버합니다.
- • data-testid로 안정적인 셀렉터 구축
- • API 인터셉트로 외부 의존성 제어
- • 명시적 대기와 Page Object Pattern 적용
- • 중요 플로우만 E2E, 나머지는 통합 테스트로 커버
결제나 회원가입 같은 크리티컬 사용자 플로우를 자동화하여 배포 전 신뢰성을 확보합니다.
E2E 테스트가 간헐적으로 실패하는 flaky test 문제를 어떻게 해결하시나요?
Q. Vue 컴포넌트의 스냅샷 테스트를 사용할 때의 장단점과 효과적인 활용 시나리오를 설명해주세요. 특히 스냅샷 테스트가 오히려 테스트 유지보수 부담을 증가시키는 안티패턴과, 이를 방지하기 위한 전략을 제시해주세요.
스냅샷의 크기, 업데이트 빈도, 그리고 의미 있는 변경 감지 능력을 기준으로 판단해보세요.
스냅샷 테스트는 순수 프레젠테이션 컴포넌트의 렌더링 결과를 빠르게 검증할 때 유용하지만, 큰 컴포넌트 트리 전체를 스냅샷으로 저장하면 작은 변경에도 업데이트가 필요해 유지보수 부담이 커집니다. 효과적으로 사용하려면 작고 안정적인 컴포넌트(아이콘, 버튼, 뱃지 등)에만 적용하고, 동적 데이터(타임스탬프, ID)는 모킹하여 일관된 스냅샷을 생성합니다. 비즈니스 로직이나 사용자 인터랙션은 명시적 단언문으로 검증하고, 스냅샷은 보조 수단으로만 사용합니다. 스냅샷 업데이트 시 diff를 반드시 리뷰하여 의도하지 않은 변경이 포함되지 않았는지 확인하고, 큰 스냅샷은 인라인 스냅샷이나 특정 속성만 검증하는 방식으로 분리합니다. Vue Test Utils의 html()이나 text() 대신 특정 요소만 선택하여 스냅샷을 생성하면 불필요한 업데이트를 줄일 수 있습니다.
- • 작고 안정적인 프레젠테이션 컴포넌트에만 적용
- • 동적 데이터 모킹으로 일관된 스냅샷 생성
- • 스냅샷 업데이트 시 diff 반드시 리뷰
- • 특정 요소만 선택하여 스냅샷 범위 최소화
디자인 시스템의 UI 컴포넌트 라이브러리에서 의도하지 않은 스타일 변경을 감지할 때 유용합니다.
스냅샷 테스트를 시각적 회귀 테스트(Visual Regression Test)로 대체하는 것은 어떤 경우에 적절한가요?
Q. Vue 프로젝트의 테스트 자동화 전략을 수립할 때, 단위 테스트, 통합 테스트, E2E 테스트의 비율과 실행 시점을 어떻게 설계하시겠습니까? 특히 CI/CD 파이프라인에서 테스트 실행 시간을 최소화하면서도 품질을 보장하는 전략과, 병렬 실행 및 선택적 테스트 실행 방법을 설명해주세요.
Testing Pyramid, 변경된 파일 기반 선택적 테스트 실행, 테스트 샤딩, 그리고 단계별 테스트 게이트를 고려해보세요.
Testing Pyramid 원칙에 따라 단위 테스트 70%, 통합 테스트 20%, E2E 테스트 10% 비율로 구성하여 빠른 피드백과 안정성을 균형있게 확보합니다. 로컬 개발 시 pre-commit 훅에서 변경된 파일과 관련된 단위 테스트만 실행하고, PR 생성 시 전체 단위/통합 테스트를 실행합니다. E2E 테스트는 main 브랜치 머지 전에만 실행하여 CI 시간을 절약합니다. Vitest의 --changed 옵션이나 Jest의 --onlyChanged를 사용해 변경 영향 범위만 테스트하고, 테스트 샤딩으로 여러 워커에 분산하여 병렬 실행합니다. 크리티컬 패스의 E2E 테스트는 매일 스케줄된 작업으로 실행하고, 실패 시 Slack 알림을 보냅니다. 테스트 실행 시간이 10분을 초과하면 병목 지점을 분석하고, 느린 테스트는 프로파일링하여 최적화하거나 분리합니다. 모노레포 환경에서는 Nx나 Turborepo의 affected 기능으로 변경된 패키지만 테스트합니다.
- • Testing Pyramid 비율로 단위 70%, 통합 20%, E2E 10% 구성
- • 변경 영향 범위 기반 선택적 테스트 실행
- • 테스트 샤딩과 병렬 실행으로 시간 최소화
- • 단계별 테스트 게이트와 스케줄된 E2E 테스트
대규모 프로젝트에서 빠른 배포 주기를 유지하면서도 품질을 보장하기 위해 효율적인 테스트 전략이 필수적입니다.
테스트 실행 시간이 계속 증가하는 문제를 어떻게 관리하시겠습니까?
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!