예전에는 프론트엔드가 복잡하지 않아 프론트엔드 테스트를 잘 진행하지 않았습니다.
그러나 프론트엔드의 중요성이 강조되고, 백엔드의 다양한 기능들이 프론트엔드로 옮겨오면서 프론트엔드가 점점 더 중요해지고, 프론트엔드를 관리 할 필요성이 생기기 시작했죠. 이 과정에서 React
, Vue
등 다양한 프론트엔드 프레임워크가 생기기 시작합니다.
typescript
도 javascript
의 자유성을 일정 부분 포기하는 대신 프론트엔드 소프트웨어를 더 잘 관리하기 위해서 만든 언어라고 할 수 있습니다.
이렇게 프론트엔드 관리와, 프론트엔드 코드의 퀄리티의 중요성이 대두 되면서 프론트엔드 테스팅도 같이 주목받기 시작합니다.
테스트는 코드가 의도한대로 동작한 다는 것을 보장해 줍니다
코드 작성 후 손으로 하나하나 동작을 확인하다보면 확인하지 못한 기능이 있을 수 있습니다. 그러나 정해진 시나리오에 맞춰 테스트를 진행한다면, 모든 시나리오가 완료되면 코드도 의도한 기능대로 동작한다는 걸 보장할 수 있습니다.
그러나 처음 코드를 작성할 때 보다 유지 보수 및 기능 수정시 테스트는 더욱 더 큰 역할을 합니다. 우리는 개발을 하다보면 코드를 개선하기위해 리팩토링을 하거나, 기능을 변경하기 위해 코드를 변경하는 경우가 많이 있습니다. 사실 새로운 기능을 개발하는 것보다 이러한 수정이 더 많죠.
코드를 수정할때 테스트가 존재하지 않는다면 코드 수정시 발생할 수 있는 사이드이팩트(의도치 않은 버그나 오류)를 알 수 없습니다. 이는 암실에서 코딩을 하는 것과 같습니다. 괜한 수정 때문에 버그가 생겼다고 후회하죠.
때문에 개발자들은 점점 기존에 동작하고 있는 코드를 수정하기 꺼려하고, 기존의 코드를 리팩토링해서 사용하기 보다는 중복된 기능을 가진 코드를 생성하는 것을 선호하게 됩니다. 프로젝트엔 중복되는 코드와 나쁜코드가 점점 더 쌓여가고 결국 프로젝트는 회생불가능한 상태가 됩니다.
자책하지 마세요. 이 것은 당신의 잘못이 아닙니다. 그 어떤 개발자라도 테스트가 없는 환경에서 이미 동작하는 코드를 마음대로 수정하는 담력을 가지고 있지 않습니다.
프론트엔드 또한 굉장히 기능이 많이지고 복잡해지고 있습니다. 당신의 프로젝트가 더욱더 복잡해지기 전에 테스트를 작성해 보는 것이 어떨까요?
프론트엔드를 테스트하기 위해서 어떤 테스트를 작성해야 할까요? 구글에 열심히 검색해보니, 유닛테스트, 통합테스트, e2e테스트가 나옵니다. 어떤 테스트를 작성해야 할까요?
단일 컴포넌트
나 단일 서비스
가 될 수 있습니다.각각의 테스트는 따로 사용하기도 같이 사용하기도 합니다. 특히 유닛 테스트와 통합 테스트는 React-Testing-Library와 같이 동시에 지원하는 툴이 많기 때문에 전략에 따라 섞어서 사용하기도 합니다.
어떻게 테스팅을 할지 정하기 위해서는
1. 어떤 도구를 사용할지 정하고
2. 어떤 범위로 테스팅을 하고
3. 어떤식으로 작성을 할지가 알아야 할 필요가 있습니다.
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해서 사용해, 서버와의 통합 없이도 테스트가 가능하게 만들 것입니다.
이 전략은 프론트엔드 테스트시 가장 많이 사용하는 전략 중 하나입니다.
단일 서비스
테스트를 위하여 axios
호출이 필요하면, 실제로 axios를 호출하는게 아니라 axios
와 인터페이스가 똑같은 mock
을 만들어 주입해줍니다. 혹은 함수 단위로 mocking을 할 수있습니다.axios.get('/abc')
를 호출하는 로직이 있으면 mock axios
에서는 /abc
라는 파라미터로 get
이 호출되면 mock에서 만든 가상의 응답을 줍니다. 그리고 테스트할때 이 mock axios
로 바꿔줍니다.테스트 시나리오를 작성할때는 주로 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를 통해서 테스팅 하는 것도 같이 해볼 예정입니다 .
카우치코딩에서는 1:1 코딩 문제해결 멘토링 서비스입니다. 가르치는데 관심있는 멘토분들이나 문제해결이 필요한 멘티분들 방문해주세요~
또한 별도로 6주 포트폴리오 수업을 진행중에있습니다. 혼자 포트폴리오 준비를 하는데 어려움이 있으면 관심가져주세요~
카우치코딩 고동휘 멘토의 글입니다.