Javascript 테스팅 with Jest #3

PEPPERMINT100·2020년 11월 15일
0

TDD

#1 에서 잠깐 언급했듯이 TDD는 Test-Driven-Development의 약자이다. 개발 방법론, 방식 중의 하나로 테스트에 의해 개발을 주도하는데, 아래와 같은 과정을 거친다.

먼저 실패하는 테스트를 작성하고,
그 다음 테스트를 성공시키기 위한 코드를 작성하고,
그 다음 필요없는 코드를 삭제하는 등 이 후 코드 관리가 쉽도록 코드를 리팩토링한다.

TDD 방식은 먼저 코드가 정상적으로 작동하는 것을 보장해주며 하나씩 기능을 단계 별로 개발해나가므로 불필요한 기능을 추가하지 않게 되고 직관적인 개발을 할 수 있게 된다.

저번 글까지는 간단한 Typescript 코드만을 테스트했지만 이번엔 많이 사용되는 라이브러리인 React에 Jest를 붙이고 Enzyme이라는 유용한 라이브러리까지 붙여서 아주아주아주 간단한 리액트 코드를 TDD 방식으로 작성해보자.

세팅

create-react-app으로 리액트앱을 실행했다면 jest 세팅은 이미 되어있다. 따라서 enzyme만 따로 설치를 해주면 된다. 하지만 이 글에서는 webpack으로 프로젝트를 생성한 코드를 기준으로 세팅을 진행해볼 것이다. 개인적으로 webpack으로 세팅을 해놓은 react 코드는 여기에서 확인할 수 있으니 이 코드를 클론해서 사용해도 된다.

제 보일러 플레이트를 사용한다면 세팅은 넘어가도 됩니다.

먼저

yarn add --dev jest enzyme enzyme-adapter-react-16 @types/jest @types/enzyme @types/enzyme-adapter-react-16

라이브러리들을 설치해준다. 만약 16버전 이하의 리액트를 사용한다면 enzyme-adapter-react-16 버전에 맞도록 16 숫자를 바꿔주면 된다.

module.exports = {
    moduleFileExtensions: ["ts", "tsx", "js"],
    setupFilesAfterEnv: ['./src/setupTests.ts'],
    testRegex: "./src/__tests__/*",
};
// jest.config.js

그리고 jest.config.js라는 파일을 만들어 위와 같이 작성한다. setupFilesAfterEnv는 테스트의 세팅을 설정해주는 파일을 지정해주고 testRegex는 테스트할 파일들을 지정해준다. 우리는 src 폴더안에 __test__라는 폴더안에 모든 테스트 파일을 저장해놓을 것이니 지금 미리 만들어주도록 하자.

그리고 src폴더 안에 setUpTest.ts라는 파일을 만들고 아래와 같이 작성한다.

import { configure } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
configure({ adapter: new Adapter() })
// setUpTest.ts

위 코드는 React의 컴포넌트를 테스트하도록 도와주는 enzyme을 실행해준다. jest.config.js에서 테스트 전에 이 파일을 실행하도록 설정을 해 둔것이다.

import React from "react";

const App:React.FC = () => {
    return(
        <div id="app">
            react
        </div>
    )
}
// App.tsx
export default App;

그리고 App.tsx 파일을 간단하게 작성하고 __test__ 폴더안에 App.test.tsx 파일을 생성하고 아래와 같이 작성한다.

import React from "react";
import { shallow, ShallowWrapper } from "enzyme";
import App from "../App";

describe("Testing App.tsx File", () => {
    let wrapper: ShallowWrapper;
    beforeEach(() => {
        wrapper = shallow(<App />);
    })
    test("App Header Test", () => {
        expect(wrapper.find("header").text()).toContain("testing with jest"); 
    })
})

shallow안에 <App />을 넣어주면 enzyme이 컴포넌트를 읽어온다. 그리고 wrapper 변수에 초기화 시킨다음 다양한 메소드로 컴포넌트를 테스트할 수 있다. 코드는 굉장히 직관적으로

expect(wrapper.find("header").text()).toContain("testing with jest"); 

이 부분은 <App /> 컴포넌트의 header 엘리먼트를 찾아서 그 안에 "testing with jest"가 들어있는지 테스트해준다. 테스트를 실행하면 테스트가 정상적?으로 실패하는 것을 볼 수 있다.

TDD 맛보기

이제 TDD 방식으로 리액트 코드를 조금씩 작성해보자. 먼저 package.jsonscripts

  "scripts": {
    "start": "webpack serve",
    "build": "webpack --mode production",
    "test": "jest --watchAll"
  }

이렇게 --watchAll 옵션을 추가해주자. 이렇게 하면 test 커맨드를 실행했을 때 다양한 옵션을 선택하도록 하는데 a 를 눌러서 모든 테스트를 실행하도록 하고 q를 누르면 테스트를 종료할 수 있다. 이 옵션을 실행하면 코드를 변경할 때마다 jest가 보고 있다가 계속 다시 테스트를 실행해주기 때문에 편하다.

여기서 우리는 버튼으로 간단히 테마를 토글하는 기능을 만든다고 하자. 먼저 App.test.tsx를 다음과 같이 작성해보자.

import React from "react";
import { shallow, ShallowWrapper } from "enzyme";
import App from "../App";

describe("Testing App.tsx File", () => {
    let wrapper: ShallowWrapper;
    beforeEach(() => {
        wrapper = shallow(<App />);
    })
    test("App Header Test", () => {
        expect(wrapper.find("header").text()).toContain("TDD App"); 
    })

     test("button text is BUTTON", () => {
        expect(wrapper.find("button").text()).toContain("BUTTON"); 
    })

    test("current theme is light", () => {
        expect(wrapper.find("#current-theme").text()).toContain("light");
    })
})

위와 같이 header, button 안에 들어갈 텍스트를 작성해준다. 그리고 find 내에 #을 사용하면 css 셀렉터와 동일하게 id 값을 지정해서 가져올 수 있다. 위와 같이 작성하고 테스트를 돌리면 전부 실패한다. 이제 테스트를 통과 시키기 위해 App.tsx 파일을 아래와 같이 작성한다.

import React, { useState } from "react";

const App:React.FC = () => {
    
    const [currentTheme, setCurrentTheme] = useState("light");
    return(
        <div id="app">
            <header>TDD App</header>
            <button>BUTTON</button>
            <p id="current-theme">{currentTheme}</p>
        </div>
    )
}

export default App;

코드를 ctrl + s 를 통해 저장하면 jest의 --watchAll 옵션으로 인해 자동으로 다시 테스트가 실행되고 전부 테스트를 통과하는 것을 확인할 수 있다. 이제 다른 기능 추가를 위해 테스트 코드를 더 작성해보자.

   test("Toggle to switch theme", () => {
        const button = wrapper.find("button");
        button.simulate('click');
        const theme = wrapper.find("#current-theme").text();
        expect(theme).toContain("dark");
    })

위 테스트 코드는 button을 가져와서 simulate 메소드를 통해 클릭을 하는 시뮬레이션을 돌린다. 그리고 또 #current-theme을 가져와서 안의 텍스트가 dark로 변했는지 확인을 한다. 코드를 저장하면 테스트가 실패하는 것을 확인할 수 있고 이제 위 테스트를 통과하기 위한 코드를 더 작성해보자.

import React, { useState } from "react";

const App:React.FC = () => {
    const [currentTheme, setCurrentTheme] = useState("light");

    const toggleTheme = () => {
        setCurrentTheme("dark");
    }

    return(
        <div id="app">
            <header>TDD App</header>
            <button onClick={toggleTheme}>BUTTON</button>
            <p id="current-theme">{currentTheme}</p>
        </div>
    )
}

export default App;

위와 같이 currentTheme의 상태를 바꿔주는 코드를 추가하고 button의 onClick 프로퍼티에 추가해주자. 그리고 테스트를 돌리면 정상적으로 테스트를 통과하는 것을 알 수 있다.

위 토글버튼은 쉬운 이해를 위한 코드로 원래 theme을 변경시키는 코드는 저렇게 작성하면 안됩니다.

결론

간단하게 jest를 통하여 typescript와 react에서 테스트를 진행해보았다. Jest에 대해 공부를 하고 코드를 작성할때마다 지금 내가 뭐하는거지? 와 같은 생각이 들었는데, React에서 테스팅을 하면서 신기한 점을 발견했다. 평소와 달리 yarn start 커맨드로 로컬 서버에 웹 페이지를 띄우지 않고 테스트의 통과 여부만 보고 리액트 코드를 작성하고 있다는 사실을 깨달았다. 만약 원래라면 로컬서버에서 버튼을 눌러보며 변하나 안변하나 콘솔에 로그를 출력해가며 개발을 했겠지만 미리 테스트 코드를 작성해놓고 돌려보니 테스트를 실행해둔 터미널만 보고 코드를 작성하고 있음을 알게 되었다. 그리고 테스트 코드의 단계를 상세하게 작성할 수록 어떤 기능을 개발하기 위해 꼭 필요한 단계만 집중해서 진행하게 될 것 같다는 느낌을 받았다. 이번 jest 시리즈에서 작성한 전체 코드는 여기에서 확인할 수 있다.

profile
기억하기 위해 혹은 잊어버리기 위해 글을 씁니다.

0개의 댓글