이번에 새로 시작하는 React 프로젝트에 테스트 코드를 적용해보면 어떨까 싶어서 요새 테스트에 빠져있는 친구에게 테스트란 한마디로 뭘까에 대해서 물어봤었다.

테스트는 말 그대로 로직이 예상대로 흘러가는지 테스트 하는 것이다
다른 곳에서 내 코드를 사용하기 전에 미리 잘 작동하는지 확인하는 것이 테스트의 중요한 부분인 것 같다. 그럼 React에서 테스트 코드를 작성시 고려해야할 점이 무엇이 있는지 한번 파헤쳐보자
테스트는 기본적으로 무언가를 검증하고 확인하는 용도로 만드는 것이고 확인하려는 것이 무엇인가에 따라 적용하는 테스트 종류가 다르다. 프론트엔드에서 흔히 크게 세 부류로 나뉘는데
E2E Test : 엔드투엔드 테스트 (기능 테스트)Integeration Test : 통합 테스트Unit Test : 단위 테스트
나는 이중에서 가장 기본에 깔려있는 단위 테스트에 대해서 집중적으로 파보려고 한다.
해당 원칙은 단위 테스트 코드에 대해서 더 나은 코드를 만들 수 있는 원칙에 관해 설명한다.
Fast : 단위 테스트는 빨라야 한다.Isolated : 단위 테스트는 독립적으로 실행되어야 한다.Repeatable : 단위 테스트는 실행할 때마다 같은 결과를 만들어야 한다.Self-validationg : 단위 테스트는 테스트를 통과했는지 아닌지 스스로 판단할 수 있어야 한다.Timely/ThoroughTimely : 단위 테스트는 프로덕션 코드가 테스트에 성공하기 전에 구현되어야 한다.Thorough : 단위 테스트는 가능한 모든 에러나 비정상적인 흐름에 대해서도 대응해야한다.테스트 코드는 DAMP(Descriptive And Meaningful Phrases)하게 작성하는게 좋다고 한다.
DAMP하다는 건 서술적이고 의미 있게 작성해서 이해하기 쉽고 유지보수가 편하게 작성하라는 뜻인 것 같다.
참조하는 글에서 예시를 갖고와서 보여주자면
// 첫 번째 테스트 코드
it('설명을 생략하면 이해가 되시나요?', async () => {
const user = userEvent.setup();
render(<Component {...preDefinedProps} />);
const buttonElement = screen.getByRole('button', { name: '버튼입니다' });
await user.click(buttonElement);
expect(doSomething).toBeCalledWith(1234);
});
// 두 번째 테스트 코드
it('설명이 없어도 대충 느낌은 오시죠?', async () => {
const something = 1234;
const doSomething = vi.fn();
const user = userEvent.setup();
render(<Component {...preDefinedProps} something={something} doSomething={doSomething} />);
const buttonElement = screen.getByRole('button', { name: '버튼입니다' });
await user.click(buttonElement);
expect(doSomething).toBeCalledWith(1234);
});
두 번째 코드는 아직 제대로 테스트 코드를 작성할 줄 모르는 내가 봐도 어떤 테스트인지 한 눈에 알아볼 수 있다. 나같은 사람들도 테스트의 흐름을 알아볼 수 있게 작성하는걸 DAMP하게 작성하라는 뜻인 것 같다.
주어진 상황에 대해 결과를 검증하는 것을 목적으로 둔 패턴입니다.
Given : 테스트를 하기 위해 세팅하는 주어진 환경When : 테스트를 하기 위한 조건, 사용자와의 상호작용Then : 결과가 의도대로 동작하는지 검증 및 확인참조하는 곳에서 예시를 또 가져오자면
it('버튼을 1회 클릭하면 1번 클릭했다는 문구가 노출된다', async () => {
// Given: 사용자와 화면이 준비되어 있고, 화면에는 버튼이 존재함
const user = userEvent.setup();
render(<Component />);
// When: 사용자가 '여기를 눌러보세요'라는 버튼을 클릭함
const buttonElement = screen.getByRole('button', { name: '여기를 눌러보세요' });
await user.click(buttonElement);
// Then: 문구가 나타나는지 검증함
expect(screen.getByText('버튼을 1번 클릭했습니다.')).toBeInTheDocument();
});
해당 코드를 통해 내가 만든 컴포넌트가 제대로 동작하는지 확인하는 것 같다. 요새 UI 테스트 툴
로는 Storybook이 너무 잘 만들어져 있어서 이런 방법이 있다고 알아두기만 해도 좋을 것 같다.
내가 글을 읽은 부분 중에서 React에서 테스트 코드 작성 시, 이 부분이 가장 중요한 것 같다.
테스트 코드를 작성할 때 해당 케이스의 목적이 무엇인지 명확하게 생각하고 테스트 코드를 작성해야 한다는 부분이다.
이것도 참조하는 곳에서 좋은 예시가 있기 때문에 해당 예시를 사용하겠습니다.
// store.ts
interface StateAndAction {
word: string;
updateWord: (newWord: string) => void;
}
const useStore = create<StateAndAction>((set) => ({
word: '사과',
updateWord: (newWord) => set({ word: newWord }),
}));
// WordWithButton.tsx
const WordWithButton = () => {
const word = useStore((state) => state.word);
const updateWord = useStore((state) => state.updateWord);
return (
<main>
<h1>
나는 {word}를 좋아한다!
</h1>
<button
type="button"
onClick={() => {
updateWord('바나나');
}}
>
좋아하는 과일 바꾸기
</button>
</main>
);
};
해당 테스트 코드는 버튼을 눌렀을 때 좋아하는 과일이 바나나로 바뀐 문구가 노출되는지 검증하는 코드입니다. 테스트 코드를 어떻게 작성해야 우리가 원하는 목적대로 작동하게 할 수 있을까?
// 첫 번째 테스트 코드
it('바나나 문자열로 updateWord 호출 시 word가 바나나로 변경된다', () => {
const { result } = renderHook(() => useStore());
act(() => {
result.current.updateWord('바나나');
});
expect(result.current.word).toBe('바나나');
});
첫 번째 테스트 코드는 내부에서 사용하는 store에 바나나라는 단어가 올바르게 적용되는지 확인하고 있습니다. 해당 코드는 store가 제대로 작동하는지 테스트 하고 있고, 컴포넌트 내부에서 사용하는 기술에 따라 또 변해야하기 때문에 우리가 원하는 목적과는 다른 목적을 가지고있습니다.
// 두 번째 테스트 코드
it('버튼 클릭 시 heading 영역의 문구가 바나나를 좋아한다는 내용으로 변경된다', async () => {
const user = userEvent.setup();
render(<WordWithButton />);
await user.click(screen.getByRole('button', { name: '좋아하는 과일 바꾸기' }));
expect(screen.getByRole('heading', { name: '나는 바나나를 좋아한다!' })).toBeInTheDocument();
});
해당 테스트 코드를 보면 버튼을 눌렀을 때 우리의 목적대로 heading 영역의 문구가 바나나를 좋아한다는 내용으로 변경됩니다. 우리가 테스트하고자 하는 것은 내부 동작이 아니라 사용자가 특정 동작을 수행했을 때 특정 영역의 문구가 정확하게 변경되는지를 확인하는 것이 중요하다.
내가 목적을 명확하게 하자는 부분이 가장 중요하다고 생각하는 것은, 이 글을 읽기 전까지는 React 컴포넌트를 테스트 시 내부에서 동작하는 과정까지 모두 테스트 과정에 포함해야 된다는 생각을 가지고 있었기 때문이다. 결국 React에서 테스트 코드를 작성할 때도 목적을 명확하게 정해놓고 테스트 코드를 작성해야 FIRST 원칙에서 종속적이지 않은 테스트 코드를 작성할 수 있을 것 같다.
참조
우아한 기술 블로그 : https://techblog.woowahan.com/17404/
잘 보고갑니다~~