반응형

프론트엔드 코드의 특성

  1. 코드의 잦은 변경(디자인)
  2. 짧은 생명주기
  3. 정량적 평가(디자인, UI/UX)의 어려움

이는 테스트의 어려움으로 이어지게 된다.
프론트엔드의 특성을 잘 살린

  1. 잦은 변경에 대응 가능한 (react-testing library)
  2. 생명 주기에 구애받지 않는 (모듈화후 통째로 교체)
  3. 시각적 요소 평가가 편하게 (storybook & chromatic)

코드, 테스트 코드를 작성하는게 좋다.

 

프론트엔드 테스트의 종류

크게 두가지 요소로 나눌 수 있다.

  1. 기능적 테스트(소프트웨어 공학적 테스트)
  2. 시각적 테스트(디자인, UI/UX 등)

 

기능적 테스트

Static Test
정적 테스트는 코드를 실행시키지 않고 테스트를 하는 것
오타나 type에러 같은 개발자의 실수로 인해 발생하는 에러를 미연에 방지
대표적 도구 : typescript, eslint, prettier

 

unit
각 모듈을 단독 실행 환경에서 독립적으로 테스트
특정 컴포넌트를 렌더링해서 깨지지 않는지 확인하는 것을 예

Button, Dialog같은 작은 단위

 

intergration
통합 테스트는 두 개 이상의 모듈이 실제로 연결된 상태를 테스트

UI와 API 간의 상호작용이 올바르게 일어나는지
state에 따른 UI의 변경이 올바르게 동작하는지

GNU, 하나의 페이지 같은 큰 단위

 

e2e
실제 사용자의 입장 및 환경에서 테스트하는 것

 

위로 갈수록 테스트 비용이 비싸지고 느려진다!

권장 테스트 비율은

unit 70%
intergration 20%
e2e 10%

 

프론트엔드 관점의 테스트

 

visual test(Visual Regression Test)

 

시각적으로 무엇이 달라졌나 테스트
정량적 요소(디자인 등)로 평가하기 어렵기 때문에 자동화가 어렵다

 

대표적 도구 : storybook, chromatic

 

chromatic - Visual Regression Test

 

chromatic을 사용하면 화면의 bitmap 단위로 비교가 가능하다
원본(snapshot)과 변경 코드가 시각적으로 무엇이 달라졌는지 분석

 

테스트 환경

 

브라우저 환경

 

장점 : 크로스 브라우징 & 기기 호환성 테스트 가능
단점 : 느리다
ex) cypress, playwright

 

Node.js 환경


장점: 빠르다
단점: 크로스 브라우징 & 기기 호환성 테스트 가능 불가
ex) jest

 

프론트엔드 테스트 대상

(시각적 요소, 사용자 이벤트 처리, API 통신)
테스트의 대상은 다음과 같이 크게 3가지

 

1. 시각적 요소

시각적 요소 - Visual Regresstion testing(chromatic 참고)

컴포넌트가 깨졌는지, side Effect가 없는지 확인 용도로 쓰고
왜 깨졌는지는 기능적 테스트에서 확인하는게 빠르다.

 

2. 사용자 이벤트 처리

프론트엔드 대부분의 로직은 다음과 같은 로직을 가진다.

  1. 1. Event가 발생한다 (click, enter event 등)
  2. 2. Handler가 event를 처리하고 Effect를 발생시킨다. <-
  3. 3. Effect는 View를 변경시킨다.

테스트에서 중점으로 처리해야 할것은 2번


이벤트가 발생하여 어떠한 효과(effect)를 가져오는가를 테스트

view, style보다는 비즈니스 로직에 중점을 둔다.
시각적 요소는 storybook, Chromatic에게 위임

 

1번은 브라우저, react library에서 알아서 처리하니 믿음을 가져라
2번이 주 로직
3번은 2번이 제대로 처리되었다면 정상적으로 동작하게됨(의존성을 가짐)

 

하지마라

  1. 컴포넌트들이 제대로 합성되는가(view?)
  2. 컴포넌트들이 화면에 제대로 렌더링 되는가(view)
  3. 컴포넌트에 어떠한 동작을 했을 때, 무엇인가 트리거가 되는가(event<->handler 연결)

 

Testing library(jest)

 

Testing-library는 이름 그대로 UI를 사용자 관점에서 테스트할 수
있도록 도와주는 라이브러리

 

내부 상태 변경과 같은 상세한 구현(implementation details)을 테스트하는
기존의 프론트엔드 테스트의 문제점을 개선하기 위해 등장

사용자는 컴포넌트 내부의 상태가 어떻게 바뀌고,
컴포넌트 내부 메서드가 어떻게 호출되는지에는 전혀 관심이 없기 때문에,
테스트 코드를 작성할 때도 이에 대해선 테스트하지 말고
사용자 관점에서 테스트하자.

 

2, 3번을 잘 처리하면 테스트가 잘 되었다는 믿음
리펙토링을 해도 잘 동작하는지 확인 가능

 

3. API 통신

 

백엔드는 정상적으로 동작한다고 가정

주 목표는 API 통신으로 받아온 데이터를 넣으면 컴포넌트가
'정상적으로 동작 하는가' 중점

크게 2가지 방식

 

1. 테스트용 서버 or 라이브러리

테스트용 서버를 가지는건 공수가 많이 들기 때문에
"MSW"라는 라이브러리 이용

서비스워커가 API를 가로채고 가짜 데이터를 응답해준다.

 

2. api mocking

api 자체를 mocking
axios를 호출하면 데이터를 전해주는 가짜 함수로 변경

취향에 따라 사용

 

최근엔 MSW를 많이 쓰는 느낌
(storybook + jest + msw 연동해서 환경설정을 구축해놓으면
시각적 & 기능적 테스트에 한꺼번에 편리하게 사용가능)

 

실제 api 테스트를 작성하다보니 느낀점 (+ 2023.3월 추가)

 

보통 리엑트 컴포넌트 코드를 나누자면

 

1. style(UI, styled component) 

2. business logic(hook, 전역 state, api 등)

 

두가지로 세분화할 수 있는데

 

간단한 api + logic이면 그냥 storybook + jest + msw 연동시켜서 테스팅 시키는 방식으로 테스트 하면 편한거 같고

(이때도 대다수의 테스트 대상은 2번이다)

 

복잡한 인터렉션 + api는 위 방식으로 테스트하기 너무 복잡한거 같다. 

(실시간 모니터링 툴을 개발중이다보니 한 컴포넌트에 연동되는 api가 3~6개가 되는 케이스도 있었다;;)

2번만 따로 jest + mocking api로 business logic를 테스트 후  

style 부분에서 연동 & 호출이 잘되는지 연동 테스트 하는 방식이 좋은거 같다.

 

예를 들어 

 

const Example = ({onClick}) => {
  return <button type="button" onClick={onClick} /> 
}

위와 같은 컴포넌트는 style만 정의되어 있고 business logic(onClick)은 다른곳에서 주입받는다고 가정해보자. (hook이나 전역 state를 사용할수도 있고 방법은 여러가지)

 

onClick이라는 함수를 jest에서 jest.fn()로 mocking하고 

tobeCalled(), tobeCalledWith()등 호출을 확인하는 함수를 사용하면 

테스트 상황이 주어졌을때 실제로 business logic을 올바르게 호출하는지 확인 가능하다. 

 

너무 내부로직이 비대해서 쪼갠 케이스인데 이런 케이스는 잘 없지 싶다

 

잘하는법? (나도 몰라..)

 

테스트는 비용이다
-> 불필요한 테스트 최소화
-> 동어반복적 테스트 방지

 

부작용을 최소화하자

로직을 순수함수로 작성한다면 항상 input이 동일하다면

마지막 output이 동일할꺼라 예측 가능하다.

 

즉, 부작용(side Effect)가 없는 순수함수의 조합으로 코드를 작성하다면

테스트시 end-to-end로 테스트가 가능하니 테스트 작성 & 유지보수가 쉬워짐

 

다만 부작용이 없는 코드는 나오기 어려우므로

UI <-> business logic <-> data(접근) 이라고 나눠본다면

 

business logic를 순수함수로 작성하고 테스트하려고 노력하고,

UI, data 부분에 부작용 함수 부분을 넘겨주자..?

 

FIRST 법칙

F (Fast 빠르게)
I (Independat 독립적으로 - sideEffect 영향 X)
R (Repeatable 반복가능하게 - 결과가 매번 동일)
S (Self-Validating 자가검증하는 - 결과가 True or False)
T (Timely 적시에 - 필요할때)

 

 

코드(테스트 대상) 짤때 SOLID 법칙 응용

약간 다르긴 하지만 본질은 비슷하다

핵심 -

  1. 함수는 하나의 기능만
  2. 모듈화(ex - 라이브러리 교체시 인터페이스는 놔두고 통째로 교체)
  3. 컴포넌트 간의 의존성 제거
  4. 로직과 스타일의 분리 (container-presenter패턴, hook 등등)

 

Given, When, Then 패턴 사용

 

test('button click event', () => {
// Given
const data = $4
const spyOn = jest.fn();
render(<button type="button" onClick={spyOn}  />)

// When
const button = screen.getByRole('button');
button.click();

// Then
expect(spyOn).tohaveBeenCalled();
})

가독성 좋은 테스트는 기능 명세서 역할도 가능하다 

유저의 행동에 중점을 두고 작성

 

Given -> 주어진 환경

When -> Event Trigger

Then -> Effect Result

 

BDD에서 파생된 개념인거 같은데 깔끔하게 시나리오를 짤 수 있어서 좋은거 같다

 

라이브러리 테스트(optional)

 

필수는 아니지만 라이브러리 버전업(or 교체)시 무엇이 변경되었는지(or 무엇이 라이브러리 종속적인지) 

빠르게 알기 쉽도록 라이브러리 관련 코드에 테스트 작성

 

또한 라이브러리(ex-버튼, 테이블 라이브러리 등등)을 사용한다면 모듈화하고 테스트를 작성해놓으면 

나중 교체할때 어떤 기능이 부족하고 무엇이 문제인지 빠르게 인지 가능하다.

 

--추가 

일하다 yarn.lock이 꼬여서 라이브러리가 터지는 정신나가는 케이스를 봐서

필수는 아니지만 틈틈히 라이브러리 테스트를 작성해놓으면 좋을꺼같다..?

 

 

 

reference

 

1. 좋은 함수 만들기 - 부작용과 거리두기 (tistory.com)

https://velog.io/@teo/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%97%90%EC%84%9C-MV-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80%EC%9A%94
https://kooku0.github.io/blog/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C%EC%97%90-solid-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0/
https://jbee.io/react/testing-1-react-testing/
https://blog.mathpresso.com/%EB%AA%A8%EB%8D%98-%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%94%EB%93%9C-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A0%84%EB%9E%B5-1%ED%8E%B8-841e87a613b2

반응형

+ Recent posts