매번 boilerplate를 만들면서 매번 만들던게 있었다.
개발의 편의성을 위해서 스타일 가이드 페이지를 만들고 같이 협업하는 개발자들에게 가이드 페이지를 확인하여 작업하라고 했었다.
이것이 불편하고 의존성이 높아져서 변경하고 싶어졌다.
매번 공통 컴포넌트들을 변경할때 마다 많은양의 공통 컴포넌트도 다같이 수정했어야 했다.
또 공통 컴포넌트를 수정하면, 비지니스 페이지 뿐만아니라 가이드 페이지의 내용까지 수정해야 정상적으로 빌드가 되었고, 빌드시에 가이드 페이지가 포함되어 번들의 사이즈가 커져서 쓸데없는 크기가 되었다.
예전부터 컴포넌트의 위주의 개발을 진행하고 있었는데 컴포넌트 독립적으로 테스트 위주의 개발을 진행하고 싶었다.
예전부터 Storybook이 비쥬얼 가이드의 역활을 하고는 있다고 알았지만, 제대로 도입해 보니 생각보다 여러가지의 역활이 있었다.
devDependencies
가 많아진다. 좀 많이 많아진다.@storybook/addon-a11y
: ui 접근성을 테스트 하기위한 에드온@storybook/addon-actions
: ui action을 확인하기 위한 에드온@storybook/addon-measure
: ui 요소의 간격, 크기와 같은 내용을 시작적으로 표현@storybook/addon-storysource
: 스토리북의 원본 소스표시@storybook/addon-viewport
: 다양한 viewport를 제공하는 에드온@storybook/test
: storybook 내부의 ui 테스트를 진행
왼쪽부터
웹 접근성이 문제되는 구간을 표시 할수 있다.
Storybook을 구성하면서 지역 단위 테스트의 필요성도 생겼다.
Storybook이 비쥬얼 테스트와 컴포넌트 단위의 개발을 담당함으로 다른단위(Service, Store등...)의 테스트 단위도 테스트가 필요해 졌다.
export default class SampleService {
iamTrue(): boolean {
return true;
}
iamFalse(): boolean {
return false;
}
iamTen(): number {
return 10;
}
}
import {describe, test, expect} from 'vitest';
import {render, screen} from '@testing-library/vue';
import NotFound from '@/app/system/view/not-found.vue';
import SampleService from '@/app/system/service/sample.service';
describe('system/not-found.vue', () => {
test('success init', () => {
render(NotFound);
screen.getByText('NOT FOUND');
});
test('sample.service.ts', () => {
const sampleService: SampleService = new SampleService();
expect(sampleService.iamFalse()).toBeFalsy();
expect(sampleService.iamTrue()).toBeTruthy();
expect(sampleService.iamTen()).toBeTypeOf('number');
expect(sampleService.iamTen()).toBeGreaterThan(9);
});
});
import type {Ref} from 'vue';
import {ref} from 'vue';
import type {AxiosResponse} from 'axios';
import {defineStore} from 'pinia';
import {Validate} from '@/core/methods';
import Container from '@/core/container';
import SampleAxios from '@/core/axios/sample.axios';
import Mapper from '@/core/service/mapper.service';
import {SampleModel} from '@/app/sample/model/sample.model';
export const useSampleStore = defineStore('album-sample-store', () => {
const sample: SampleAxios = Container.resolve(SampleAxios);
const mapper: Mapper = Container.resolve(Mapper);
const list: Ref<SampleModel.Response.FindAll[]> = ref([]);
const one: Ref<SampleModel.Response.FindOne | null> = ref(null);
function setList(id: number, params: SampleModel.Request.Search) {
if (Validate(params)) {
return sample.get(
'/albums',
{params}
).then((response: AxiosResponse<SampleModel.Response.FindAll[]>) => {
list.value = mapper.toArray(SampleModel.Response.FindAll, response.data);
}
);
}
return;
}
function getList(): SampleModel.Response.FindAll[] {
return mapper.toArray(SampleModel.Response.FindAll, list.value);
}
return {list, one, setList, getList};
});
describe('sample/sample-list.vue', () => {
beforeEach(() => {
setActivePinia(createPinia());
});
test('pinia test', async () => {
const search: SampleModel.Request.Search = new SampleModel.Request.Search();
const sampleStore = useSampleStore();
const setSpy = vi.spyOn(sampleStore, 'setList');
const getSpy = vi.spyOn(sampleStore, 'getList');
await sampleStore.setList(1000, search);
const lists = sampleStore.getList();
expect(setSpy).toHaveBeenCalled();
expect(getSpy).toHaveBeenCalled();
expect.arrayContaining(lists);
});
});