October 07, 2020
Front-End 에는 다양한 테스트 영역이 존재한다.
E2E, Unit, UI 테스트 등.. 테스트는 개인적으로 주관이 많이 개입되는 영역이라고 생각한다. ` 그래서 각자가 생각하는 테스트의 개념이 다르지만, 그 Goal 은 소프트웨어 품질로 동일하다.
이 글은 테스트를 다뤄 보는 목표 지향적이며, 세세한 함수들이나 명렁어는 다루지 않는다.
test-universe(이하 ‘TU’ 라고 칭함) 는 간단한 TO-DO 앱을 통해 테스트 코드를 전반적으로 다뤄 본다.
jest 는 JavaScript 테스팅 프레임워크다.
먼저 jest 공식 문서의 설명을 보면
Jest is a delightful JavaScript Testing Framework with a focus on simplicity.
직역 하자면 Jest는 단순함에 초점을 맞춘 JavaScript Testing Framework 이다.
Jest는 다양한 JavaScript의 테스트 프레임워크 중 하나인데, create-react-app 에 내장 되어있다.
test/${name}.test.js 에 존재하는 파일들은 모두 jest CLI 를 통해 테스트 가능하다.
프로그램을 테스트 할때는 반드시 ‘Assertion: 단언’ 이라는게 필요하다.
비교할 두 대상을 가지고 참을 따지는걸 코드 상으로 풀이하는 것이다.
BDD 방식의 API가 사용된다.
function sum(a, b) {
return a + b;
}
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3); //단언문
});
위 코드의 단언문을 보면 sum(1, 2) 을 통해 나온 합산 값이 3이여야 한다.
합산 값이 3이 나온다면 test는 성공이고, 아니라면 fail 이다.
아래에서 TU의 유닛 테스트를 확인 해보자.
testing-library
은 모든 테스트를 DOM 위주로 진행한다.
컴포넌트의 구현 방식과 상관없이 결과만 같다면 테스트에 실패하지 않는다.
즉, 행위주도 테스트를 가능하게 하는 유틸이다.
input
은 입력을 받고 그 출력을 해당 input
에 보여주는데 여기서 2가지 테스트가 있다.
onChage
이벤트가 정상 발생 하는가//Input.js
import React from "react";
import PropTypes from "prop-types";
export const Input = ({ placeholder, color, value, onChange, ...rest }) => {
return (
<input
data-testid="todoInput"
type="text"
placeholder={placeholder}
style={color && { color }}
onChange={onChange}
value={value}
{...rest}
/>
);
};
Input.propTypes = {
placeholder: PropTypes.string,
color: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func
};
Input.defaultProps = {
placeholder: "투두리스트 입력",
color: "black",
value: "",
onChange: undefined
};
//Input.test.js
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { Input } from "../Input";
describe("Input", () => {
it("should call onchange event and change value", () => {
const mockFn = jest.fn(); //임시의 목업 함수 생성.
const changedValue = "lion";
render(<Input onChange={mockFn} value="test" />); //input 컴포넌트를 render함수로 그려준다.
const input = screen.getByTestId("todoInput"); //input의 'date-testid' attr를 가져온다
fireEvent.change(input, { //fireEvent는 어떤 이벤트를 발생시킬때 사용한다.
target: { value: changedValue }
});
input.value = changedValue; //input의 value를 직접적으로 변경해준다.
expect(mockFn).toBeCalled(); //목업 함수가 콜이 됐으면 true
expect(input.value).toBe(changedValue); // value가 'lion' 으로 변경됐다면 true
});
});
List
는 data 배열을 순회 하면서 todo들을 만들어주는 컴포넌트다.
1가지 테스트가 존재한다.
li
를 그려 주는가span의 onClick
이벤트는 유틸 함수라서 유틸에서 유닛테스트 에서 진행한다.
//List.js
import React from "react";
import PropTypes from "prop-types";
import { css } from "@emotion/core";
export const List = ({ data, deleteTodoList }) => {
return (
<ul data-testid="todoList">
{data.map((value, index) => {
return (
<li
key={index}
css={css`
margin: 1em 0;
text-align: center;
list-style: none;
display: flex;
justify-content: space-between;
width: 100%;
`}
>
{value}
<span
css={css`
margin-left: 0.5em;
border: 1px solid black;
padding: 0.5em;
font-size: 0.8rem;
`}
style={{ cursor: "pointer" }}
onClick={() => {
deleteTodoList(index);
}}
>
X
</span>
</li>
);
})}
</ul>
);
};
List.propTypes = {
data: PropTypes.array.isRequired,
deleteTodoList: PropTypes.func.isRequired
};
//List.test.js
import React from "react";
import { render } from "@testing-library/react";
import { deleteOne } from "../../utils";
import { List } from "../List";
describe("List", () => {
it("should render as long as the data", () => {
const mockData = ["첫번째", "두번째"];
render(<List data={mockData} deleteTodoList={deleteOne} />); //deleteTodoList props는 필수로 설정되어있기때문에 필수로 넘겨주어야 한다.
const li = document.querySelectorAll("li");
expect(li.length).toEqual(mockData.length); //render된 li의 갯수가 mockData 길이와 일치하는지 비교
});
});
재사용 가능한 함수들을 유틸로 만들고, 각각의 유틸들에 대한 유닛 테스트를 진행한다.
유닛테스트를 하기 제일 적합한 환경이다.
//utils/index.js
export const deleteOne = ({ list, key }) => {
const newList = [...list];
const filtered = newList.findIndex((_value, index) => {
return index === key;
});
newList.splice(filtered, 1);
return newList;
};
//index.test.js
import { deleteOne } from "../../utils";
describe("utils", () => {
it("should delete one of list data", () => {
const a = { list: ["a", "b", "c"], key: 0 }; //a,b,c 중 0번째 index가 지워진 배열이 결과로 나와야한다.
const b = deleteOne(a);
expect(b).toStrictEqual(deleteOne(a)); //stringEqual을 통해 값과 타입까지 같이 비교한다.
});
});
yarn test:watch
실행 시키면 위와 같은 결과물을 볼수 있다.
5개의 Test suites