프론트엔드 테스트 해야할까? - (1)

couchcoding·2022년 10월 19일
7
post-thumbnail

프론트엔드도 반드시 테스트를 해야하는 이유

예전에는 프론트엔드가 복잡하지 않아 프론트엔드 테스트를 잘 진행하지 않았습니다.
그러나 프론트엔드의 중요성이 강조되고, 백엔드의 다양한 기능들이 프론트엔드로 옮겨오면서 프론트엔드가 점점 더 중요해지고, 프론트엔드를 관리 할 필요성이 생기기 시작했죠. 이 과정에서 React, Vue등 다양한 프론트엔드 프레임워크가 생기기 시작합니다.
typescriptjavascript의 자유성을 일정 부분 포기하는 대신 프론트엔드 소프트웨어를 더 잘 관리하기 위해서 만든 언어라고 할 수 있습니다.
이렇게 프론트엔드 관리와, 프론트엔드 코드의 퀄리티의 중요성이 대두 되면서 프론트엔드 테스팅도 같이 주목받기 시작합니다.

왜 코드 관리와 코드 퀄리티 향상을 위해서 테스트가 필요할까요?

테스트는 코드가 의도한대로 동작한 다는 것을 보장해 줍니다
코드 작성 후 손으로 하나하나 동작을 확인하다보면 확인하지 못한 기능이 있을 수 있습니다. 그러나 정해진 시나리오에 맞춰 테스트를 진행한다면, 모든 시나리오가 완료되면 코드도 의도한 기능대로 동작한다는 걸 보장할 수 있습니다.

그러나 처음 코드를 작성할 때 보다 유지 보수 및 기능 수정시 테스트는 더욱 더 큰 역할을 합니다. 우리는 개발을 하다보면 코드를 개선하기위해 리팩토링을 하거나, 기능을 변경하기 위해 코드를 변경하는 경우가 많이 있습니다. 사실 새로운 기능을 개발하는 것보다 이러한 수정이 더 많죠.

코드를 수정할때 테스트가 존재하지 않는다면 코드 수정시 발생할 수 있는 사이드이팩트(의도치 않은 버그나 오류)를 알 수 없습니다. 이는 암실에서 코딩을 하는 것과 같습니다. 괜한 수정 때문에 버그가 생겼다고 후회하죠.
때문에 개발자들은 점점 기존에 동작하고 있는 코드를 수정하기 꺼려하고, 기존의 코드를 리팩토링해서 사용하기 보다는 중복된 기능을 가진 코드를 생성하는 것을 선호하게 됩니다. 프로젝트엔 중복되는 코드와 나쁜코드가 점점 더 쌓여가고 결국 프로젝트는 회생불가능한 상태가 됩니다.
자책하지 마세요. 이 것은 당신의 잘못이 아닙니다. 그 어떤 개발자라도 테스트가 없는 환경에서 이미 동작하는 코드를 마음대로 수정하는 담력을 가지고 있지 않습니다.

프론트엔드 또한 굉장히 기능이 많이지고 복잡해지고 있습니다. 당신의 프로젝트가 더욱더 복잡해지기 전에 테스트를 작성해 보는 것이 어떨까요?

유닛테스트, 통합테스트, e2e테스트

프론트엔드를 테스트하기 위해서 어떤 테스트를 작성해야 할까요? 구글에 열심히 검색해보니, 유닛테스트, 통합테스트, e2e테스트가 나옵니다. 어떤 테스트를 작성해야 할까요?

유닛테스트

  • 코드의 Unit단위를 테스트 합니다. 프론트엔드 코드에서 유닛은 단일 컴포넌트단일 서비스가 될 수 있습니다.
  • 프로젝트의 제일 작은 단위를 테스트하기 때문에 모든 시나리오를 테스트할 때 테스트의 양이 가장 적습니다. (효율적입니다.)
  • TDD 사용시 가장 작은단위 부터 기능대로 동작하는지 확인하면서 개발하기 좋습니다.
  • 소프트웨어의 구조가 명확하지 않고, 단일 유닛의 기능이 자주 변경되는 환경에서는 테스트가 자주 변경되어야 하고 이는 테스트 작성의 효율을 떨어뜨리기 때문에 도전적인 프로젝트에 사용하는 것은 추천하지 않습니다
    -> Unit 단위로 요구사항을 테스트하는데 잦은 리팩트링으로 Unit이 변경된다면 이때마다 테스트를 변경해야할 것입니다. 소프트웨어의 구조가 명확한 프로젝트에서 사용하는 것을 추천드립니다.
  • 별도의 통합이 필요없기 때문에 백엔드 API가 구현이 안되어도 작성할 수 있습니다.

통합테스트

  • 통합된 기능을 테스트합니다. 유닛테스트가 단일 컴포넌트나 단일 서비스를 기준으로 테스트를 했다면, 유닛들 간의 데이터를 주고받는 환경을 테스트합니다.
  • 통합테스트 전략에 따라 프론트엔드 전체 유닛들만 통합하여 테스트하거나, 백엔드와 데이터베이스까지 전체를 통합해서 테스트하는 경우도 있습니다.
  • 전체 유닛의 상호작용에 대해서 테스트가 가능합니다.

e2e테스트

  • e2e는 end to end의 약자로, 소프트웨어의 가장 끝단인 사용자로 부터 가장 끝단인 백엔드 인프라까지 테스트하는 것을 의미합니다.
  • e2e테스트는 cypress, playwright 등의 도구를 통해 브라우저 동작을 자동화 하여 테스트를 진행합니다.
  • 주로 유저 스토리를 기반의 시나리오를 기반으로 테스트를 진행합니다.
  • 사용자 관점에서 전체 시나리오를 테스트하기 때문에 코드의 변경에도 테스트를 변경할 필요 없습니다.
  • 다른 테스트와 달리 전체 시나리오를 테스트하기 떄문에 소프트웨어의 무결성을 보장하기 가장 좋습니다.
  • 사용자 관점에서 전체 시나리오를 테스트하기 때문에 전체를 테스트하기 위해선 굉장히 많은 테스트가 필요합니다.
  • 프론트엔드만 따로 테스트하는 것이 불가능하며, 백엔드와의 통합이 필요합니다
  • 전체 테스트를 하는데 시간이 오래걸려 자주 테스트하기 어렵습니다

각각의 테스트는 따로 사용하기도 같이 사용하기도 합니다. 특히 유닛 테스트와 통합 테스트는 React-Testing-Library와 같이 동시에 지원하는 툴이 많기 때문에 전략에 따라 섞어서 사용하기도 합니다.

구체적으로 어떻게 테스트 코드를 작성할까?

어떻게 테스팅을 할지 정하기 위해서는
1. 어떤 도구를 사용할지 정하고
2. 어떤 범위로 테스팅을 하고
3. 어떤식으로 작성을 할지가 알아야 할 필요가 있습니다.

1. React Testing Library

React Testing Library는 CRA를 통해 리액트 앱을 만들때 기본으로 제공되는 라이브러리로, 리액트 컴포넌트를 가상으로 랜더링하고 동작을 확인하고 랜더링 결과를 확인할 수 있는 도구입니다.

// React Testing Library 공식 예제
import {render, screen} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import '@testing-library/jest-dom'
import Fetch from './fetch'

test('loads and displays greeting', async () => {
  // Given 
  // 컴포넌트를 랜더링
  render(<Fetch url="/greeting" />)

  // When
  // Load Greeting 글씨를 찾아서 클릭
  await userEvent.click(screen.getByText('Load Greeting'))

  // Then
  // heading에 hello there이 있는지 검증
  expect(screen.getByRole('heading')).toHaveTextContent('hello there')
})

가장의 Dom에서 ReactComponent를 html로 랜더링하고 Text가 랜더링 되었는지, 확인할 수 있습니다.
Unit Test환경이나 Integration 환경에서 추천합니다.

e2e테스트로는 Cypress, Playwrite를 사용할 수 있는데 별도의 포스팅을 통해 소개하도록 하겠습니다.

무엇을 테스트할까?

테스트를 하기 전 제일 먼저 먼저 고려할 것은 어디서부터 어디까지 테스트를 할 것 인가입니다.
완벽한 유닛테스트를 위해서는 모든 컴포넌트를 분리해서 테스트를 할 수 있고, 완벽한 통합 테스트를 위해서는 서버와 프론트를 전부 띄운 후 브라우저단에서 테스트를 할 수 있습니다.
본 예제에서 사용할 테스트 전략은 컴포넌트 단위로 테스트를 하되, 연관된 컴포넌트는 같이 테스트를 합니다. 단! api호출이 필요한 경우 호출 컴포넌트를 mocking해서 사용해, 서버와의 통합 없이도 테스트가 가능하게 만들 것입니다.
이 전략은 프론트엔드 테스트시 가장 많이 사용하는 전략 중 하나입니다.

  • mocking이란?
    mocking은 실행을 위해 특정 컴포넌트가 필요하면, 해당 컴포넌트를 mock이라는 가짜 컴포넌트로 교체하는 기술을 의미합니다. 예를 들어 단일 서비스 테스트를 위하여 axios 호출이 필요하면, 실제로 axios를 호출하는게 아니라 axios와 인터페이스가 똑같은 mock을 만들어 주입해줍니다. 혹은 함수 단위로 mocking을 할 수있습니다.
    axios.get('/abc')를 호출하는 로직이 있으면 mock axios에서는 /abc라는 파라미터로 get이 호출되면 mock에서 만든 가상의 응답을 줍니다. 그리고 테스트할때 이 mock axios로 바꿔줍니다.
    최근에는 API 라이브러리를 교체나 추상화를 염두해두고 Mock Api 서버를 사용해서 Mock을 만들기도 합니다.
    Mock API 서버는 API의 호출을 가로채 가상의 서버 응답을 줍니다.

Given When Then 전략

테스트 시나리오를 작성할때는 주로 Given When Then을 패턴을 이용하여 테스트를 진행합니다.
먼저 given은 주어진 조건에 대한 코드를 작성합니다. 테스트할 데이터나 컴포넌트를 초기화 하는로직이 들어갈 수 있습니다.
when은 테스트할 행동에 대한 코드를 작성합니다.
then은 테스트 검증 로직에 대해서 작성합니다.

만약 plus라는 함수를 테스트한다면

// given
const a = 1;
const b = 2;

// when
const result = plus(a,b);

// then
assert result === 3;

이런식으로 데이터가 주어지고 어떤 동작을 했을때 어떤 결과가 나오는지에 대해 기술하면 됩니다.
또한 then 에서는 반드시 검증하는 데이터가 로직을 담고있지 않아야 합니다.

// 안좋은 테스트의 예
assert result === a+b;

위와 같이 테스트를 값이 아니라 로직끼리 비교한다면, 테스트 검증 로직은 우리가 검증하려는 코드와 같아집니다. 이는 무의미한 코드 중복과 명확하지 않은 테스트를 만듭니다.

다음 글에선

다음글에선 React Testring Library를 통해서 실제로 테스팅 하는 예제를 알려드리겠습니다.
Redux를 통해서 테스팅 하는 것도 같이 해볼 예정입니다 .

About Couchcoding

카우치코딩에서는 1:1 코딩 문제해결 멘토링 서비스입니다. 가르치는데 관심있는 멘토분들이나 문제해결이 필요한 멘티분들 방문해주세요~
또한 별도로 6주 포트폴리오 수업을 진행중에있습니다. 혼자 포트폴리오 준비를 하는데 어려움이 있으면 관심가져주세요~

카우치코딩 고동휘 멘토의 글입니다.

profile
포트폴리오 수업 & 코딩 멘토링 서비스 카우치코딩입니다.

0개의 댓글