테스트를 작성해보자 ! 좋아요 ! 근데 뭘요? (feat. jest)

Jongco·2023년 5월 22일
2
post-thumbnail

Intro

사이드 프로젝트를 진행하면서 QA기간에 버그 하나를 고치면 두개가 생기는 마법을 경험했다.
또한 코너케이스를 확인하지 않고, 무작정 구현에만 몰두하다 보니 놓치는 것이 많았다.

1차 배포는 완료했지만 2차 배포에 들어가기 전에 기존의 기능을 완성도 있게 마무리 하고,
마구 작성한 냄새가 나는(?) 코드를 리팩터링 하고자 했다.

하지만 하나를 고치면 여기 저기서 터져나오는 에러를 감당하지 못했고,
이를 해결하기 위해 테스트 코드를 작성하기로 했다.

테스트 코드를 작성하기로 결정한 후, jest, testing-library를 셋팅했다.
이제 테스트를 작성해보자 !

좋아요! 근데 뭘요?

...

진짜 딱 이 기분이었다. 아무리 구글링해도 테스트를 작성하는 방법만 써있을 뿐..
어떤 것을 테스트 해야 하는지에 대해서는 해답을 찾지 못했다..

단위 테스트를 많이 작성하라는대, 그럼 단순히 버튼 컴포넌트도 테스트를 작성해야 되는 건가?

const Button = ({text, onClick}) => {
	return <button onClick={onClick}>{text}</button>
}

이 컴포넌트에 대해서는 그럼 text를 전달했을 때 잘 출력 되는지 onClick함수가 한번만 호출되는지 테스트 하면 되는 건가????

라는 고민을 했다. 이 고민의 답은 아래 문장에서 해결했다.

만약 코드가 너무 간단해서 오류가 날 확률이 거의 없다면, 테스트를 하지 않는 편이 낫다.
- Extreme Programming Explained 중 -

사실 텍스트를 전달한 데로 출력하는 것은 너무 간단하다. 그래서 테스트를 작성할 필요가 없다고 판단했다.

좋아 ! 그럼 너무 간단한 컴포넌트 제외하고 어느정도 합쳐진 컴포넌트부터 테스트 해볼까???

const DetailList = ({
  dateFilterProperty,
  mode,
  selectedEventId,
  details,
  setSelect,
  setOpenUserDetails,
}: Props) => {
 
  ...
  
}

이 컴포넌트를 보자마자 테스트를 하고싶은 생각이 싹 사라졌다.

  1. 만약에 props 이름 바뀌면 구현사항이 바뀌지 않아도, 테스트가 깨질 것이 분명했고
  2. setState를 어떻게 전달해야 할지 너무 막막했다.

...

다시 원점이다.

다시 원점으로

테스트에 대한 글만 정말 많이 찾아다녔다. 하지만 내가 원했던

지금 당장 이 프로젝트에서 뭘 테스트 해야 돼?

에 관한 답은 얻지 못했다.

이대로라면 아무 것도 못하고, 시간 낭비할 것이 분명하다.
그래서 처음으로 돌아가 내가 테스트를 왜 하려고 했지에서 다시 고민을 해봤다.

테스트를 작성하는 것은 오류 방지, 문서화 등이 있지만
본질적으로 생각해 보면 결국 사용자에게 부정적인 경험을 주고싶지 않아서 작성하는 거잖아??
/
그럼 사용자 입장에서 생각해보면 되겠네 !!
/
엇..! 그렇다면 결국 기획했던 유저 플로우로 테스트 하면 되지 않을까??
어차피 내부 로직이 변경되어도 기획만 변경되지 않으면 사용자에게 그려줄 페이지는 동일하니까!!

이렇게 난 컴포넌트에 대한 단위 테스트를 일단은 건너 뛰고 통합 테스트를 하기로 결정했다.

프로젝트를 살펴보자

우측 상세 내역에서 유저가 할 수 있는 시나리오를 정리해봤다. 시나리오는 아래와 같다.

1. 월간 필터링
	1-1. 월간 필터링이 걸려있을 때에는 "05월"로 표시되어야 한다.
    1-2. 날짜 옆에 화살표를 클릭하면 한달씩 이동해야 한다.
    1-3. 필터링을 변경했을 경우
    	1-3-1. 월간 -> 주간으로 변경했을 경우 첫째주인 "04월 30일 - 05월 06일"이 출력되어야 한다.
        1-3-2. 월간 -> 일간으로 변경했을 경우 첫날인 "05월 01일"이 출력되어야 한다.     
        
2. 주간 필터링 << 현재 이미지에 선택되어 있는 필터링 ⭐️
	2-1. 주간 필터링이 걸려있을 때에는 "05월 14일 - 05월 20일"로 표시되어야 한다.
    2-2. 날짜 옆에 화살표를 클릭하면 한주씩 이동해야 한다.
    2-3. 필터링을 변경했을 경우
    	2-3-1. 주간 -> 월간
        	- 1주차일 경우 "04월 30일 - 05월 06일"이지만 월간으로 변경 시 5월 내역이 출력되어야 한다.
            - 마지막 주차일 경우에도 5월 내역이 출력되어야 한다.
        2-3-2. 주간 -> 일간
        	- 1주차일 경우 "04월 30일"이 아닌 "05월 01일"이 출력되어야 한다.
            - 1주차를 제외하고 해당 주차의 일요일이 출력되어야 한다.

3. 일간 필터링
	3-1. 일간 필털깅이 걸려있을 때에는 "05월 14일"로 표시되어야 한다.
    3-2. 날짜 옆에 화살표를 클릭하면 하루씩 이동해야 한다.
    3-3. 필터링을 변경했을 경우
    	3-3-1. 일간 -> 월간으로 변경했을 경우 "05월"이 출력되어야 한다.
        3-3-2. 일간 -> 주간으로 변경했을 경우 해당 주차인 "05월 14일 - 05월 20일"이 출력되어야 한다.

이렇게 시나리오를 만든 후 이 시나리오에 대한 테스트만을 작성했다.

이제 테스트코드를 작성해보자..!!! 😵‍💫

드디어 테스트 코드

시나리오를 바탕으로 작성한 테스트 코드 중 일부분이다.

	// 1-2.날짜 옆에 화살표를 클릭하면 한달씩 이동해야 한다.
    it('월간 필터링일 경우 오른쪽 화살표 버튼 클릭 시 다음달로 넘어가야 한다', async () => {
      //Arrange(준비)
      user = userEvent.setup();
      render(withRouter(<DetailFine />, '/group/17/book/detail'));
		
      //Act(
      await user.click(screen.getByText('월간'));
      await user.click(screen.getByTestId('list_skip_right'));
      await user.click(screen.getByTestId('list_skip_right'));
		
      //Assert
      expect(screen.getByText('07월')).toBeInTheDocument();
    });



	// 1-3-1 월간 -> 주간으로 변경했을 경우 첫째주인 "04월 30일 - 05월 06일"이 출력되어야 한다.
    it('월간 클릭 후 주간 필터링을 클릭했을 때 "04월 30일 - 05월 06일"이 표기되어야 한다 ', async () => {
      //Arrange
      user = userEvent.setup();
      render(withRouter(<DetailFine />, '/group/17/book/detail'));
  
      //Act
      await user.click(screen.getByText('월간'));
      await user.click(screen.getByText('주간'));

      //Assert
      expect(screen.getByText("04월 30일 - 05월 06일")).toBeInTheDocument();
    });

확실히 이제는 코드를 수정하면 직접 필터링을 걸고 날짜를 변경하면서 확인하기 보다
저장하고 3초만 멍때리면 위와 같이 어디가 어떻게 잘못된 건지 알려준다 !!

그런데 약간은 불편한 게 있다. Arrange 단계가 중복된다. 중복은 못참지 바로 수정해준다.

	let user;

	beforeEach(() => {
      user = userEvent.setup();
      render(withRouter(<DetailFine />, '/group/17/book/detail'));
    })


	// 1-2.날짜 옆에 화살표를 클릭하면 한달씩 이동해야 한다.
    it('월간 필터링일 경우 오른쪽 화살표 버튼 클릭 시 다음달로 넘어가야 한다', async () => {
      await user.click(screen.getByText('월간'));
      await user.click(screen.getByTestId('list_skip_right'));
      await user.click(screen.getByTestId('list_skip_right'));
		
      expect(screen.getByText('07월')).toBeInTheDocument();
    });



	// 1-3-1 월간 -> 주간으로 변경했을 경우 첫째주인 "04월 30일 - 05월 06일"이 출력되어야 한다.
    it('월간 클릭 후 주간 필터링을 클릭했을 때 "04월 30일 - 05월 06일"이 표기되어야 한다 ', async () => {
      await user.click(screen.getByText('월간'));
      await user.click(screen.getByText('주간'));

      expect(screen.getByText("04월 30일 - 05월 06일")).toBeInTheDocument();
    });	

이렇게 테스트를 작성해 놓음으로써 자신감을 갖고 코드를 변경할 수 있었다.

결론

테스트는 구체적인 내부 구현사항이 아닌 동작을 테스트해야 한다.

솔직히 이렇게 작성한 테스트 코드가 좋은 코드인지는 잘 모르겠다.
하지만 이런 코드라도 없을 때보다 생산성이 향상된 것은 정말 체감이 된다.

처음에는 구현에만 맞춰 작성했기 때문에 테스트하기 좋지 않은 코드도 현재 많이 존재한다.
2차 배포에는 TDD를 통해 좀 더 테스트 하기 쉽고, 좋은 설계를 가진 코드를 작성해 보려한다..!!

해당 글에서 단위 테스트에 대한 내용은 없었지만, 당연히 단위 테스트도 중요하다.
실제로 프로젝트의 주요 비즈니스 로직에 대해서는 단위 테스트를 작성한 후에 리팩터링을 진행했다.

하지만 버튼같은 정말 단위가 되는 컴포넌트에서는 아직 테스트의 필요성을 느끼지 못했다.
주로 치명적인 오류는 단위 하나하나에서 발생하는 것이 아니라,
컴포넌트들이 서로 통합되고, 비즈니스 로직이 합쳐졌을 때 발생한다고 생각한다.

그런데 만약 치명적 오류의 방지가 목적이 아닌 더 나은 완성도를 위해
즉, 레이아웃이 깨지거나 css가 디자인과 약간 달라지는 것을 막기 위해서의 테스트는
추후에 고려해 볼만 하다고 생각이 든다!

아래는 고민의 흔적..

참조

0개의 댓글