본문 바로가기
프로젝트/Fullstack

[Svelte] 유닛 테스트 코드 작성방법

by bingual 2025. 2. 3.
반응형

소개

 

 

GitHub - bingual/sveltekit5: svelte5, 기타 라이브러리 테스트

svelte5, 기타 라이브러리 테스트. Contribute to bingual/sveltekit5 development by creating an account on GitHub.

github.com

 

해당 프로젝트는 최신 `Svelte 5`와 `SvelteKit`을 기반으로 구축된 프로젝트로, 클린 코드와 유지 보수를 중점적으로 고려한 웹 애플리케이션입니다.

 

성능 최적화, 재사용 가능한 컴포넌트 설계, 모듈화, 메모리 누수 관리, 중복 제거, 테스트 코드 작성을 목표로 하여 효율적인 개발 환경을 구성하는데 초점을 맞춘 기능 테스트가 목표입니다.

 

`Svelte 5` 는 2024년 10월 19일 정식으로 릴리즈 되었습니다.

 

한국에서는 `React`, `Vue`, `Next`와는 다르게 인지도가 낮지만 빠르고 편리하며 프레임워크 내에서 자체적으로 제공하는 기능이 많아 개발 경험이 좋기에 해외에서는 핫한 프레임워크 중 하나입니다.

 

현재 사용하는 `Svelte 5` 및 기타 라이브러리들은 베타 버전도 포함하고 있기에 버그가 있을 수 있습니다.

앞으로 연재될 Svelte 5 포스팅들은 단순히 기능을 구현하는 것만이 아니라 어떻게 해야 효율적으로 기능들을 구현하고 사용 및 관리하는지에 대해 설명을 진행할 예정입니다.

Sveltekit, Prisma, Tailwind, Auth 등등 해당 프로젝트에 사용되는 라이브러리에 대한 설명은 없으므로 공식문서를 참고 바랍니다.

프로젝트 구조

prisma/               # Prisma 관련 설정 및 파일 폴더
e2e/                  # E2E 테스트 코드 폴더
src/                  # 소스 코드 루트 폴더
├── lib/              # 재사용 가능한 라이브러리 코드 폴더
│   ├── auth.ts       # Oauth 코드
│   ├── prisma.ts     # Prisma 인스턴스 코드
│   ├── supabaseClient.ts  # Supabase 관련 코드
│   ├── utils/        # 유틸리티 코드 폴더
│   └── components/  # Svelte 컴포넌트 폴더
├── routes/          # SvelteKit 라우팅 폴더
└── tests/           # 유닛 테스트 코드 폴더

명시되지 않은 코드 구조가 있을 수 있습니다.

 

 

내용 요약

설명은 간단하게 진행되니 자세한 설명은 참조하는 링크를 참고해 주세요.

  • `vitest` 및 테스트 코드 설명
  • `happy-dom`을 이용한 브라우저 `mock` 적용
  • `clients` `servers` 테스트 코드 환경 분리

https://github.com/capricorn86/happy-dom

https://vitest.dev/

 

Vitest 설명

기존에는 Jest와 같은 테스팅 프레임 워크를 많이 사용했었습니다. 하지만 Vite가 번들러 중 가장 성능이 우수하고 개발자 경험이 좋기 때문에 Vitest라는 차세대 테스트 프레임워크를 사용함으로 좀 더 나은 테스트 환경과 생산성을 가져갈 수 있습니다. 

 

`jest`는 각 빌드에 따라 맞는 설정을 해야 할 필요성도 있고 모킹과 스냅샷 기능을 사용하기 위해 별 다른 설정이 더 필요합니다. 하지만 `vite`를 번들러로 사용한다면 `vitest` 하나만으로 전부 해결할 수 있습니다.

 

예전 `ssr`과 다르게 매우 발달한 현재 `ssr` 기능을 테스트 하기에도 가장 적합하죠.

 

테스트 코드를 작성하는 이유는 무엇일까요?

기존에 완성해놓은 기능이 있다면 언젠가는 이 기능을 리팩터링을 하거나 업데이트를 해야 하는 경우가 생깁니다.

 

해당 기능에 대한 개발문서가 없다면 그 기능에 얽혀있는 수 많은 함수가 제대로 작동하는지 매번 직접 확인을 해보는 불필요한 디버깅을 위한 시간이 발생하게 됩니다. 개발 문서가 있더라도 여러 테스트케이스가 제대로 작동하는지 알기가 힘드며 그 함수의 기능을 파악하는 데에도 시간이 걸리고 기능이 변경되면 그에 맞춰 문서도 변경되어야만 하죠.

 

즉 온전히 개발하는것에 집중하기보다 완성된 기능을 유지보수 하는데에 더 시간이 많이 쓰이는 경우가 발생하게 됩니다.

 

하지만 해당 기능에 사용되는 함수들에 여러 테스트 케이스를 사전에 미리 작업을 해놓고 테스트 코드를 작성하게 된다면? 

  • 개별 함수나 모듈이 예상대로 동작하는지 검증하여 버그를 사전에 방지합니다.
  • 기존 코드의 동작을 유지하면서 안전하게 수정 및 개선할 수 있습니다.
  • 반복적인 수동 테스트를 줄이고, 자동화된 검증을 통해 개발 효율을 높입니다.
  • 테스트 코드 자체가 기능을 설명하는 예제가 되어, 코드 이해도를 높입니다.
  • 새로운 변경 사항이 기존 기능에 영향을 미치지 않도록 검증합니다.
  • 배포 전 자동화된 테스트를 통해 예상치 못한 오류를 줄입니다.

유닛 테스트는 코드의 안정성, 유지보수성, 개발 생산성을 높이는 필수적인 도구입니다.

 

 

Vitest 사용 예제

`vite.config.ts`

export default defineConfig({
  ...
  test: {
    include: ['src/**/*.{test,spec}.{js,ts}'],
    environment: 'happy-dom',
  },
});

 

브라우저 환경을 모킹하기 위해 happy-dom 패키지를 설치해주고 환경설정을 해줍니다.

`src/tests/servers/`, `src/tests/clients/` 각각 서버, 클라이언트 환경을 분리해 줍니다.

 

 

[Svelte] 구독 기능을 이용한 토스트 메시지 구현 해당 파트에서 로컬 스토리지 매니저를 만들었습니다. 이 함수를 가지고 간단하게 어떻게 테스트 코드를 작성하는지 살펴볼까요?

 

`clients/variables.spec.ts`

import { localStorageManager } from '$lib/utils/variables';

import { beforeEach, describe, expect, it, vi } from 'vitest';

vi.mock('$app/environment', () => ({
  browser: true,
}));

describe('localStorageManager (Browser 환경)', () => {
  const mockKey = 'testKey';
  const mockValue = { name: 'test', age: 30 };

  beforeEach(() => {
    localStorage.clear();
  });

  it('객체를 localStorage에 저장할 수 있어야 한다', () => {
    const { saveToLocalStorage, loadFromLocalStorage } = localStorageManager();

    saveToLocalStorage(mockKey, mockValue);
    const storedValue = loadFromLocalStorage(mockKey);

    expect(storedValue).toEqual(mockValue);
  });

  it('존재하지 않는 키에 대해 null을 반환해야 한다', () => {
    const { loadFromLocalStorage } = localStorageManager();

    const result = loadFromLocalStorage('nonExistentKey');
    expect(result).toBeNull();
  });

  it('localStorage를 비울 수 있어야 한다', () => {
    const { saveToLocalStorage, clearLocalStorage, loadFromLocalStorage } = localStorageManager();

    saveToLocalStorage(mockKey, mockValue);
    clearLocalStorage();

    const result = loadFromLocalStorage(mockKey);
    expect(result).toBeNull();
  });
});

 

 

해당 함수가 제대로 작동하는지 확인하기 위해서는 브라우저 환경에서 실행하고 직접 로컬 스토리지에 값이 등록되어있는지 확인해야 하니 `browser`을 활성화합니다.

 

 

`servers/variables.spec.ts`

vi.mock('$app/environment', () => ({
  browser: false,
}));

describe('localStorageManager (Non-Browser 환경)', () => {
  const mockKey = 'testKey';
  const mockValue = { name: 'test', age: 30 };

  beforeEach(() => {
    localStorage.clear();
  });

  it('브라우저 환경이 아니라면 undefined를 반환해야한다.', () => {
    const { saveToLocalStorage, clearLocalStorage, loadFromLocalStorage } = localStorageManager();

    saveToLocalStorage(mockKey, mockValue);
    clearLocalStorage();

    const result = loadFromLocalStorage(mockKey);
    expect(result).toBeUndefined();
  });
});

 

만약 Non-Broswer 환경이라면 따로 모킹을 해주면 됩니다.

 

 

Vitest 메서드 설명

`describe`: 테스트 그룹

`it`: 단위 테스트 블록

`expect`: 검증 함수

메서드 사용법은 링크를 참조하세요.

 

테스트를 위한 그룹을 만들고 해당 그룹에서 `it`과 `expect`를 이용한 테스트 케이스를 여러 개 만들고 검증합니다.

테스트 코드는 너무 복잡하게 작성하는 것이 아니라 가독성에 포인트를 맞추고 작업을 해야 읽기도 쉽고 검증하기에도 편합니다.

 

 

마무리

개별
그룹
파일
감시

 

이렇게 각 단위의 테스트 케이스별, 그룹별, 파일별, 감시 기능을 통해서 테스트해볼 수가 있습니다.

`감시` 기능은 핫리로드와 동일한 기능을 합니다.

 

테스트 코드를 작성하기 쉬운 코드는 그만큼 모듈화가 잘 되어 있고, 유지보수성이 높은 코드이니 테스트 코드를 잘작성하다 보면 조금 더 자신의 코드를 고도화 시키기 수월해질겁니다.