Vue 3.0/03.SFC와 Vite

[vue 3] 04. vitest를 이용한 단위 테스팅 1

  • -

이번 포스트에서는 vitest를 이용한 단위테스트에 대해 살펴보자.

 

vue와 테스트

 

테스트의 종류

일반적으로 테스트는 단위 테스트, 통합 테스트, 엔드투엔드 테스트로 크게 나눠볼 수 있다.

 

단위 테스트(Unit Test)는 가장 빈번히 사용되는 테스트로 하나의 단위(함수, 컴포넌트)로 테스트 케이스를 작성한다. 단위 테스트는 종속성이나 다른 요소와의 복잡한 상호 작용이 없는 테스트이다. 이를 위해 연관된 부분에 대해서는 가짜 객체인 Mock을 이용하기도 한다. 단위 테스트는 테스트 비용이 가장 저렴하고 테스팅 속도가 매우 빠르다.

통합 테스트(Integration Test)는 다른 요소와의 종속성을 처리하므로 좀 더 복잡도가 높아진다. 쉽게 생각하면 기존에 Mock으로 테스트하던 부분이 실제 구현부분으로 대체되어 여러 모듈간 상호 작용을 테스트 한다고 보면 쉽다.

마지막 엔드투엔드 테스트(E2E, End to End Test)는 사용자와 앱의 상호 작용에 대한 테스트이다.  예를 들어 웹의 경우 실제 browser를 이용해서 UI 테스트를 진행해서 전체 앱을 테스팅하며 인수 테스트 형태를 띈다.

 

vue에서의 테스트

vue에서는 프로젝트 생성 시 unit test와 End-to-End 테스트를 위한 옵션을 설정할 수 있다.

vite의 unit test 프레임워크는 vitest가 사용된다. 앞으로의 포스팅 과정에서는 vitest를 이용한 단위 테스트를 진행해볼 예정이다. 

End-To-End 테스트를 위해서는 가장 대표적인 프레임워크인 Cypress 등을 지원하는데 E2E는 간단히 컨셉을 이해할 수 있는 동영상을 참고하기로 하자.

https://www.youtube.com/watch?v=eM7fjPzHVa4 

 

 

vitest 기본

 

vitest

vitest는 vite 기반의 프로젝트에서 사용되는 단위 테스트 프레임워크이다.

https://vitest.dev/

 

Vitest

A blazing fast unit test framework powered by Vite

vitest.dev

 

환경 설정

프로젝트를 생성할 때 vitest 옵션을 선택하면 package.json을 살펴보면 추가된 dependency들과 script를 볼 수 있다.

"scripts": {
  "dev": "vite",
  "test:unit": "vitest",
}, 

"devDependencies": {
  "jsdom": "^22.1.0",
  "@vue/test-utils": "^2.4.1",
  "vitest": "^0.34.4",
}

테스트를 실행하려면 script에 추가된 test:unit을 실행해주면 된다

devDependcncies에 추가된 의존성은 다음과 같다.

  • vitest: unit test의 runner
  • jsdom: 테스트 과정에서 사용하게 될 dom simulator
  • @vue/test-utils: 일반적으로 VTU(Vue Testing Utils)라고 불리며 vue.js의 component를 테스트하기 위한 기능 제공

추가로 vitest를 위한 환경 설정 파일인 vitest.config.js 파일도 제공되는데 그냥 기본 옵션으로 사용해도 충분하다.

import { fileURLToPath } from 'node:url'
import { mergeConfig, defineConfig, configDefaults } from 'vitest/config'
import viteConfig from './vite.config'

export default mergeConfig(
  viteConfig,
  defineConfig({
    test: {
      environment: 'jsdom',
      exclude: [...configDefaults.exclude, 'e2e/*'],
      root: fileURLToPath(new URL('./', import.meta.url))
    }
  })
)

 

참고로 vitest를 프로젝트 생성 시 추가하지 못했다면 npm 명령으로도 추가할 수 있다.

npm install -D vitest

하지만 이렇게 추가했을 경우는 jsdom이나 vitest.config.js 파일을 별도로 만들어야 하기 때문에 조금 불편함이 따른다.

 

부가적인 library/plugin

추가적으로 테스트를 좀 더 편하게 하기 위한 녀석들을 설치해보자.

먼저 vitest ui 라는 모듈을 웹 서비스를 이용해서 테스트를 관리할 수 있게 한다.

npm i -D @vitest/ui

vitest/ui 를 실행하려면 vitest--ui 라고 명령을 입력하면 된다. 따라서 package.json의 script의 vitest에 --ui 옵션을 추가해두면 편리하다.

"test:ui": "vitest --ui"

 

다음으로 vscode를 통해서 개발중이라면 vitest plugin이 도움된다. 

이 플러그인은 vitest에서 소개하는 무려 공식 플러그인인데 IDE에서 테스트를 관리할 수 있으며 바로가기가 가능하다. 데모에 보면 실행도 되는데 현재 버전에서는 지원하지 않는것 같기도 하고 아직은 뭔가 완성이 안된듯 하다.(버전이 0.2.42라 그런가.)

 

vitest api를 이용한 간단한 테스팅

 

테스트 파일의 작성

일단 묻지마 테스트 파일을 작성해보자. vitest를 이용하기 위해서 테스트 파일의 이름은 .test.js 또는 .spec.js로 끝나야 한다.

import { describe, test, expect, it, beforeEach, afterEach, beforeAll, afterAll } from 'vitest'

describe('describe는 여러 테스트를 그룹핑 해서 관리', () => {
  test('테스트 내용 명시', () => {
    expect('test'.length).toBe(4)
  })
  it('split을 이용한 문자열 분리', () => {
    expect('hello unit test'.split(' ').length).toBe(3)
  })
})

 

test 와 describe

test는 어떤 기능(함수)를 실행했을 때 기대치(expect)의 집합으로 it라고 사용하기도 한다.

(name: string | Function, fn: TestFunction, timeout?: number | TestOptions) => void

TestFunction에서는 연된된 expect들을 여럿 작성할 수 있다.

describe는 여러 개의 test를 그룹해서 관리하기 위해서 사용된다. 

(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void

테스트 파일의 top level에 test를 작성하면 test들 별로 suite를 구성해서 test가 많아지면 복잡해지는데 describe를 사용하면 관련 있는 테스트들을 묶어서 suite를 구성할 수 있어서 관리가 용이해진다.

 

expect

expect는 assertion 즉 단정문을 사용하기 위해서 사용되는 함수이다. 단정문은 chai assertions(https://www.chaijs.com)을 기반으로 만들어졌으며 jest(react의 테스팅 도구) 형태도 동일하게 지원한다.

describe("assertion style", ()=>{
    test("chai api", ()=> expect(Math.sqrt(4)).to.equal(2))
    test("jest api", ()=> expect(Math.sqrt(4)).toBe(2))
})

가끔 하나의 test에서 여러개의 expect를 처리할 때가 있는데 이때 expect.soft라는 함수도 제공된다. expect는 테스트 과정에서 하나라도 오류가 나면 바로 테스트를 종료하고 이후의 expect는 고려하지 않는다. 반면 expect.soft는 이후에 나온 테스트도 계속 진행하게 된다.

test('expect.soft test 1', () => {
    expect.soft(1 + 1).toBe(3) // assertion은 실패했으나 다음 expect 진행
    expect.soft(1 + 2).toBe(4) // 이 assertion을 체크함
})

test('expect.soft test 2', () => {
    expect(2 + 2).toBe(3)      // assertion 실패 후 test 자체가 종료됨
    expect.soft(2 + 2).toBe(4) // 이 assertion을 체크하지 않음
})

 

테스트 라이프 사이클 관리

테스트 별로 공통적으로 처리할 내용이 있다면 beforeEach, afterEach, beforeAll, afterAll 함수를 이용할 수 있다.

beforeEach, afterEach는 각각 매 test 호출되기 전/후에 필요한 전처리, 후처리를 작성할 수 있다. beforeAll, afterAll은 모든 테스트의 전/후에 필요한 전/후 처리를 작성한다.

let i=0;

beforeEach(async ()=>console.log(`매 테스트 이전 - ${i++}`))
afterEach(async ()=> console.log(`매 테스트 이후 - ${i++}`))
beforeAll(async ()=>console.log(`모든 테스트 이전 한번 - ${i++}`))
afterAll(async ()=> console.log(`모든 테스트 이후 한번 - ${i++}`))

2개의 test 실행 결과

위 lifecycle callback은 파일에 선언된 모든 테스트에 영향을 끼치게 된다. 만약 describe 내부에 선언하는 경우는 해당 describe에만 영향을 끼친다.

위에서는 callback을 모두 async 형태로 만들었지만 꼭 그럴 필요는 없다. 비동기 작업이 필요할 때만 async 로 처리해주면 된다.

 

 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.