목록으로
AI Skills로 Vue 개발하기

AI Skills로 Vue 개발하기 (4) - Vite, Vitest, 그리고 테스트 자동화

2026년 02월 05일9 min read
#Vue#Claude Code#AI#Vite#Vitest#Testing

목차 보기
시리즈 목차 보기4 / 6

이전 편 요약

3편에서는 Pinia의 Setup Store 패턴으로 useAuthStore를 만들고, Vue Router 네비게이션 가드로 인증 흐름을 제어했다. 코드가 세 편에 걸쳐 꽤 쌓였다. 컴포넌트, composable, store, router 설정까지 갖춰진 상태다. 이쯤 되면 "이게 진짜 잘 돌아가나?" 확인할 수단이 필요하다. 이번 편에서는 Build-tooling 스킬을 활용해 빌드와 테스트 환경을 구성한다.

Build-tooling 스킬의 범위

Build-tooling 스킬에는 Vite, Vitest, tsdown, pnpm, Turborepo 가이드가 통합되어 있다. 빌드 도구 하나하나의 설정법을 외우는 대신, 스킬이 상황에 맞는 레퍼런스를 자동으로 참조해서 설정을 생성한다. "Vite proxy 설정해줘"라고 하면 Vite 가이드를, "테스트 환경 구성해줘"라고 하면 Vitest 가이드를 읽는 식이다.

Vite 설정 최적화

Vite는 개발 시 네이티브 ESM을 사용하고, 빌드 시에는 Rollup(차기 버전에서는 Rolldown)을 사용한다. 빠른 HMR과 최적화된 번들링을 동시에 얻을 수 있는 구조다. alias, proxy, 환경 변수 관리, chunk 분리까지 한 파일에서 처리된다.

ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': '/src',
    },
  },
  server: {
    proxy: {
      '/api': {
        target: 'http://localhost:3000',
        changeOrigin: true,
      },
    },
  },
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['vue', 'pinia', 'vue-router'],
        },
      },
    },
  },
})

manualChunks로 vue, pinia, vue-router를 별도 vendor 청크로 분리했다. 앱 코드가 변경되어도 vendor 청크는 캐싱이 유지되므로 배포 시 사용자가 다시 받아야 하는 용량이 줄어든다. 스킬이 이 패턴을 기본으로 제안해주기 때문에 매번 Rollup 문서를 뒤질 필요가 없었다.

Vitest 환경 구성

Vitest는 Vite 기반 테스트 프레임워크다. vite.config와 설정을 공유하기 때문에 alias나 plugin을 이중으로 설정할 필요가 없다. 테스트 환경만 별도로 지정하면 된다.

DOM 환경으로는 happy-dom을 선택했다. jsdom보다 가볍고 빠르다. 대부분의 컴포넌트 테스트에서 jsdom 수준의 완전한 브라우저 에뮬레이션은 필요하지 않다.

Antfu 컨벤션에 따라 test() 대신 describe/it 패턴을 사용한다. coverage 프로바이더는 v8이다. 별도 바이너리 설치 없이 Node.js 내장 기능으로 커버리지를 측정할 수 있다.

ts
/// <reference types="vitest" />
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

export default defineConfig({
  plugins: [vue()],
  test: {
    globals: true,
    environment: 'happy-dom',
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html'],
      include: ['src/**/*.{ts,vue}'],
      exclude: ['src/**/*.d.ts'],
    },
  },
})

컴포넌트 테스트

@vue/test-utils와 Vitest 조합으로 2편에서 만든 DataTable 컴포넌트를 테스트한다. mount로 컴포넌트를 렌더링하고, DOM 쿼리와 이벤트 트리거로 동작을 검증하는 구조다.

ts
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import DataTable from '@/components/DataTable.vue'

const mockItems = [
  { id: 1, name: '서버 A', status: 'active' },
  { id: 2, name: '서버 B', status: 'inactive' },
]

const columns = [
  { key: 'name', label: '이름', sortable: true },
  { key: 'status', label: '상태' },
]

describe('DataTable', () => {
  it('아이템 목록을 렌더링한다', () => {
    const wrapper = mount(DataTable, {
      props: { items: mockItems, columns },
    })
    expect(wrapper.findAll('tbody tr')).toHaveLength(2)
  })

  it('정렬 가능한 컬럼 클릭 시 sort 이벤트를 발생시킨다', async () => {
    const wrapper = mount(DataTable, {
      props: { items: mockItems, columns },
    })
    await wrapper.find('th').trigger('click')
    expect(wrapper.emitted('sort')).toBeTruthy()
  })
})

테스트 설명을 한국어로 작성한 건 의도적이다. it 블록의 문자열은 테스트 리포트에 그대로 출력되는데, 팀 내 커뮤니케이션 언어와 일치시키면 실패 리포트를 읽을 때 인지 부하가 줄어든다.

Store 단위 테스트

3편에서 만든 useAuthStore의 단위 테스트다. Pinia store 테스트에서 핵심은 setActivePinia(createPinia())로 매 테스트마다 독립된 Pinia 인스턴스를 생성하는 것이다. 이걸 빠뜨리면 테스트 간 상태가 공유되어 순서에 따라 결과가 달라지는 flaky test가 만들어진다.

ts
import { beforeEach, describe, expect, it } from 'vitest'
import { createPinia, setActivePinia } from 'pinia'
import { useAuthStore } from '@/stores/auth'

describe('useAuthStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
  })

  it('초기 상태는 로그아웃이다', () => {
    const store = useAuthStore()
    expect(store.isLoggedIn).toBe(false)
    expect(store.user).toBeNull()
  })

  it('logout 호출 시 상태가 초기화된다', () => {
    const store = useAuthStore()
    store.token = 'test-token'
    store.logout()
    expect(store.token).toBeNull()
  })
})

AI로 테스트 코드 생성할 때의 차이

Build-tooling 스킬의 Vitest 레퍼런스가 로드된 상태에서 테스트 코드를 생성하면, 스킬이 없을 때와 확연한 차이가 난다.

스킬이 적용된 경우, describe/it 패턴을 사용하고 happy-dom 환경을 전제로 코드를 생성한다. Pinia 테스트에서는 setActivePinia 격리 패턴이 자동으로 포함된다. 스킬이 없는 경우, test() 함수를 사용하고 jsdom을 기본으로 잡는다. Pinia 격리 패턴이 누락되어 테스트가 서로 간섭하는 코드가 생성되기도 했다.

작은 차이처럼 보이지만, 테스트 수가 늘어나면 격리 누락 하나가 디버깅에 몇 시간을 잡아먹는다. 스킬이 이런 보일러플레이트를 자동으로 처리해주는 게 실무에서 가장 체감되는 부분이었다.

Turborepo로 모노레포 빌드 관리

프로젝트가 모노레포 구조라면 Turborepo로 태스크 파이프라인을 관리할 수 있다. turbo.json에서 lint, test, build의 의존 관계를 선언하면 Turborepo가 의존 순서를 지키면서 병렬 실행 가능한 태스크는 동시에 돌린다.

Remote caching을 활성화하면 한 번 빌드한 결과를 팀원 전체가 공유할 수 있다. 로컬에서 이미 빌드된 패키지를 CI에서 다시 빌드하지 않아도 되므로 CI 파이프라인 시간이 크게 줄어든다. Build-tooling 스킬에 Turborepo 설정 가이드도 포함되어 있어서, 파이프라인 구성을 요청하면 프로젝트 구조에 맞는 turbo.json을 바로 생성해준다.

정리

Vite로 빌드 환경을 최적화하고, Vitest로 컴포넌트와 store의 단위 테스트를 작성했다. Build-tooling 스킬이 Vitest의 설정, 테스트 패턴, Pinia 격리 같은 세부사항을 자동으로 잡아주면서 테스트 코드 품질이 한 단계 올라갔다.

네 편에 걸쳐 프로젝트 셋업부터 컴포넌트 설계, 상태 관리, 빌드와 테스트까지 다뤘다. 다음 편에서는 Nuxt 3로 풀스택 확장과 Design 스킬을 다룬다.

검색...

검색...