1. 실습
1. React Testing Library
1. useDidMount
-
useDidMount: 컴포넌트가 마운트 되었을 때 주어진callback함수를 실행하는 훅💡 mount(마운트)
: 컴포넌트가 처음 화면에 렌더링될 때
const useDidMount = (callback: VoidFunction) => { const didMountRef = useRef<boolean>(false); useEffect(() => { if (didMountRef.current) return; didMountRef.current = true; callback(); }, []); }; export default useDidMount;useRef훅을 통해 생성한ref참조 변수의 초기값은false.useEffect를 통해mount되었음을 감지했을 때ref.current값을true로 변경하며callback함수 실행
`useDidMount.test.ts` 전체 코드
describe('useDidMount', () => {
it('default export이여야 한다', () => {
expect(useDidMount).toBeDefined();
});
it('effectCallback이 실행되어야 한다', () => {
const effectCallback = jest.fn();
renderHook(() => useDidMount(effectCallback));
expect(effectCallback).toBeCalled();
});
it('rerender 시 1번 실행되어야 한다', () => {
const effectCallback = jest.fn();
const { rerender } = renderHook(() => useDidMount(effectCallback));
rerender();
expect(effectCallback).toBeCalledTimes(1);
});
describe('useDidMount Component', () => {
const STATE_CHANGE_BUTTON_TEXT = 'change';
const mockCallback = jest.fn();
const App = () => {
const [_, setState] = useState(false);
useDidMount(mockCallback);
return (
<div>
<button type="button" onClick={() => setState((prev) => !prev)}>
{STATE_CHANGE_BUTTON_TEXT}
</button>
</div>
);
};
afterEach(() => {
jest.clearAllMocks();
});
it('mockCallback이 실행되어야 한다', () => {
render(<App />);
expect(mockCallback).toBeCalledTimes(1);
});
it('mockCallback은 상태가 변해도 1번 실행되어야 한다', () => {
render(<App />);
expect(mockCallback).toBeCalledTimes(1);
const setStateButton = screen.getByText(STATE_CHANGE_BUTTON_TEXT);
fireEvent.click(setStateButton);
expect(mockCallback).toBeCalledTimes(1);
});
});
});
-
**
useDidMount.test.ts**에 대한 설명describe('useDidMount', () => { ... });describe: 여러 관련 테스트들을 그룹화하는 역할
- 여기서는
useDidMount에 관한 테스트 케이스들을 그룹화한다
default exporttest
it('default export이여야 한다', () => {
expect(useDidMount).toBeDefined();
});
useDidMount가 정의되어 있는지 확인
effectCallback실행 test
it('effectCallback이 실행되어야 한다', () => {
const effectCallback = jest.fn();
renderHook(() => useDidMount(effectCallback));
expect(effectCallback).toBeCalled();
});
renderHook함수를 사용하여useDidMounthook을 실행하며, 이때effectCallback을 인자로 전달jest.fn()을 사용하여 가짜 함수 (mock function)를 생성하여 전달
- 마지막으로,
expect함수를 사용하여effectCallback이 호출되었는지 검증
rerender시 실행 횟수 test
it('rerender 시 1번 실행되어야 한다', () => {
const effectCallback = jest.fn();
const { rerender } = renderHook(() => useDidMount(effectCallback));
rerender();
expect(effectCallback).toBeCalledTimes(1);
});
- 컴포넌트가 다시 렌더링될 때
effectCallback이 한 번만 호출되어야 함을 확인
-
useDidMount Component test
describe('useDidMount Component', () => { ... });- 실제 React 컴포넌트 (
App) 내에서useDidMounthook의 동작을 검증하는 테스트 케이스들을 정의
- 실제 React 컴포넌트 (
- App 컴포넌트 정의
useState와useDidMount를 사용하는 간단한 컴포넌트- 버튼을 클릭하면 상태가 토글 됨
- 모든 테스트 후 mock 함수 초기화
afterEach(() => {
jest.clearAllMocks();
});
- 각 테스트 케이스 실행 후에 모든 mock 함수들의 호출 정보를 초기화
- App 컴포넌트 테스트
useDidMounthook이App컴포넌트에서 제대로 동작하는지, 그리고 상태가 변화해도mockCallback이 한 번만 호출되는지 확인하는 테스트 케이스들임
2. useDebounce
// src/hooks/useDebounce.ts
export default function useDebounce(value: string, delay = 300) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
};
}, [value, delay]);
return debouncedValue;
}
- useDebounce에 대한 자세한 내용 : Throttle과 Debounce를 구현해보자
`useDeboune.test.ts` 전체 코드
describe("useDebounce", () => {
jest.useFakeTimers();
it("동일한 값을 즉시 반환해야 한다", () => {
const { result } = renderHook(() => useDebounce("test", 300));
expect(result.current).toBe("test");
});
it("값이 변경되지 않았다면 지연 후에도 동일한 값을 반환해야 한다", () => {
const { result } = renderHook(() => useDebounce("test", 300));
act(() => {
jest.advanceTimersByTime(300);
});
expect(result.current).toBe("test");
});
it("지연 시간이 지나기 전까지는 디바운스된 값이 업데이트되지 않아야 한다", () => {
const { result, rerender } = renderHook(
({ value }) => useDebounce(value, 300),
{
initialProps: { value: "initial" }
}
);
rerender({ value: "updated" });
act(() => {
jest.advanceTimersByTime(250);
});
expect(result.current).toBe("initial");
act(() => {
jest.advanceTimersByTime(50);
});
expect(result.current).toBe("updated");
});
});
useDeboune.test.ts에 대한 설명
-
setTimeout, setInterval, clearTimeout, clearInterval을 가짜로 만들어준다.
describe("useDebounce", () => { // .. jest.useFakeTimers(); } -
즉시 실행했을 때
describe("useDebounce", () => { // .. it("동일한 값을 즉시 반환해야 한다", () => { const { result } = renderHook(() => useDebounce("test", 300)); expect(result.current).toBe("test"); }); }renderHook을 이용하여 훅을 실행한다.
이 때, debouncedValue는 result.current에 들어가게 된다.
-
값이 변경되지 않았을 때
describe("useDebounce", () => { // .. it("값이 변경되지 않았다면 지연 후에도 동일한 값을 반환해야 한다", () => { const { result } = renderHook(() => useDebounce("test", 300)); act(() => { jest.advanceTimersByTime(300); }); expect(result.current).toBe("test"); }); }jest.advanceTimersByTime();를 이용해서 300ms시간을 흘려 보냈다.
하지만, 그 사이에 값 변동이 없었기 때문에 동일한 값을 return한다.
-
값을 변경했을 때
describe("useDebounce", () => { // .. it("지연 시간이 지나기 전까지는 디바운스된 값이 업데이트되지 않아야 한다", () => { const { result, rerender } = renderHook( ({ value }) => useDebounce(value, 300), { initialProps: { value: "initial" } } ); rerender({ value: "updated" }); act(() => { jest.advanceTimersByTime(250); }); expect(result.current).toBe("initial"); act(() => { jest.advanceTimersByTime(50); }); expect(result.current).toBe("updated"); }); }renderHook을 실행하는데, 두 번째 인자의 initiaProps에 value로 initial을 넣는다. 그리고, value를 updated로 리렌더링을 수행한다.
250ms가 지났을 때는 여전히 initial이지만, 50ms가 더 지났을 때는 updated로 값이 변경되어 있다.
2. cypress
1. 사용자가 input에 값을 입력 하면 url이 바뀌는 지 테스트한다
describe("SearchInput", () => {
it("Input에 값을 입력했을 때, 페이지를 라우팅한다", () => {
cy.visit("/");
cy.getInput("search").type("test");
cy.waitAndReload(500); // input은 300ms의 debounce가 걸려있다
cy.url().should("include", "keyword=test");
});
});
-
cy.visit(”/”)- cypress.config.js에서 baseUrl: "http://localhost:3000"을 설정했기 때문에
cy.visit에서 path경로만 작성한다.
- cypress.config.js에서 baseUrl: "http://localhost:3000"을 설정했기 때문에
-
cy.getInput("search").type("test");-
getInput은 command.ts에서 input태그를 가져오는 로직을 모듈화한 함수이다.
Cypress.Commands.add("getInput", (name: string) => { cy.get(`input[name="${name}"]`); });
-
-
cy.waitAndReload(500);-
waitAndReload는 기다리고 reload하는 로직을 모듈화한 함수이다.
Cypress.Commands.add("waitAndReload", (relay = 300) => { cy.wait(relay); cy.reload(); });이 때, input은 300ms의 debounce가 적용되어 있기 때문에 여유롭게 500ms를 기다리고 reload한다.
-
-
cy.url().should("include", "keyword=test");
- url이 keyword=test을 포함하고 있는지 체크한다.
- 만약, 포함하고 있지 않다면 테스트 fail을 출력한다.
2. 사용자가 버튼을 클릭하면 url이 바뀌는 지 테스트한다
describe("PaginationNav", () => {
it("페이지를 새로고침 했을 때, 이전에 입력한 페이지 번호가 그대로 남아있다", () => {
cy.visit("/");
cy.wait(300);
cy.getButton("page-2").click();
cy.waitAndReload();
cy.url().should("include", "page=2");
});
});
위와 비슷하다. 대신, getInput이 아니라 id값을 기반으로 button을 찾는 getButton메서드를 사용했다.
3. 개념 정리
0. Test 종류
1. E2E Test
: End to End Test, 유저가 앱을 이용하며 눈에 보이는 것들을 테스트
- e.g. Cypress
2. Unit Test
: 유저가 볼 수 없는 웹/앱의 내부의 세세한 데이터까지 테스트
- e.g. React Testing Library, Jest
3. Integration Test
: 개발자가 변경할 수 없는 부분(외부 라이브러리, db)까지 묶어서 검증할 때 사용되는 테스트
- Unit을 넘어서 각기 다른 시스템이 잘 상호작용 하는지 (ex. 내 앱이 db와 잘 연동되는지)를 확인하는 작업이기 때문에, Unit test code를 작성할 때보다 더욱 복잡하게 만들어지며, 더 많은 코드를 테스트하기 때문에 에러 검출이 명확하지는 않다. 그래서 실제로는 Unit test에 더욱 초점을 두는 것이 좋다.
1. Jest
: Javascript All-in-one Testing Library
- 페이스북에서 만듬
- 원래 Frontend에서만 쓰였으나 최근 Backend에서도 기존의 Javascript Testing Library를 대체하는 중
- Test Framework라고 부를 정도로 다른 Test Library를 모두 커버함
- Test Runner, Test Matcher, Test Mock까지 모두 해결
특징
-
test.js로 끝나거나, test폴더 안에 있는 파일들은 모두 테스트 파일로 인식
-
아래와 같은 일정 패턴으로 작성
test("테스트 설명", () => { expect("검증 대상").toXxx("기대 결과"); }); -
예시
test("1 is 1", () => { expect(1).toBe(1); });
Matcher함수
toBe(): 원시값 비교toEqual(): 객체 비교toBeTruthy()/toBeFalshy(): true/false값인지 testtoHaveLength()/toContain(): 배열의 길이 test / 특정 원소를 가지고 있는 지 testtoMatch(): 정규표현식 testtoThrow(): 예외처리
Jest.config.js
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
resetMocks: true, // 각 테스트 사이에 mock들을 재설정
moduleDirectories: ['.yarn'], // 모듈을 검색할 디렉토리 목록 (.yarn)
testEnvironment: 'jsdom', // 테스트 환경 설정
testRegex: '(/__tests__/.*|(\\.|/)(test))\\.[jt]sx?$', // 테스트로 간주될 파일 패턴 (__
test__폴더 하위 파일, .test)
collectCoverageFrom: ['**/*.{js,ts,jsx,tsx}'], // 커버리지를 수집할 파일 패턴 (.js,.ts,.jsx,.tsx)
moduleFileExtensions: ['js', 'jsx', 'json', 'ts', 'tsx'], // 모듈 파일 확장자를 나열
transform: { // 특정 파일 확장자를 처리하기 위한 트랜스포머를 지정 (esbuild-jest)
'^.+\\.tsx?$': 'esbuild-jest',
},
coverageThreshold: null, // 테스트 커버리지 임계값을 설정
setupFilesAfterEnv: ['./jest.setup.js'], // 테스트 환경이 설정된 후 실행될 설정 파일 목록 (jest.setup.js)
};
module.exports = createJestConfig(customJestConfig);
Jest.setup.js
import '@testing-library/jest-dom'; // Testing Library에서 제공하는 Jest 확장 도구
import '@testing-library/jest-dom/extend-expect'; // 기본 Jest expect기능에 대한 추가적인 matcher 제공
import 'jest-plugin-context/setup'; // Test Spec에서 context함수 사용할 수 있게 해줌
jest.mock('next/router', () => require('next-router-mock'));
window.matchMedia = (query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
});
Package.json
{
"scripts":{
// ..
"test": "jest",
"test:watch": "jest --watch",
},
"devDependencies":{
"types/jest":~,
"types/jest-plugin-context":~,
"babel-jest":~,
"esbuild-jest":~,
"eslint-plugin-jest":~,
"jest":~,
"jest-dom":~
"jest-environment-jsdom":~
"jest-plugin-context":~
"ts-jest":~
}
}
- scripts
test:jest명령어를 실행하여 모든 테스트를 실행
test:watch:jest를 watch 모드로 실행하여 변경된 파일에 대한 테스트만 재실행
- devDependencies
types/jest: Jest에 대한 TypeScript 타입 정의- TypeScript를 사용하는 프로젝트에서 Jest를 사용할 때 필요
-
types/jest-plugin-context:jest-plugin-context라이브러리의 TypeScript 타입 정의 -
babel-jest: Babel을 사용하여 Jest 테스트를 변환하는데 사용- Babel을 사용하는 프로젝트에서 필요
-
esbuild-jest: ESBuild를 사용하여 Jest 테스트를 빠르게 변환하는데 사용💡 ESBuild?
: JavaScript 번들을 빠르게 읽을 수 있는 CLI, NPM package
- 잘 짜여진 documentation과 쉬운 CLI 환경을 갖추고 있으며 결과적으로 매우 빠름
- Javascript와 CSS 등을 배포가능한 형태로 링크 가능
- Building & conde splitting : Javsacript 및 CSS 소스를 번들링한거나 코드 분할할 수 있음
-
eslint-plugin-jest: ESLint에 Jest 규칙을 추가하기 위한 플러그인 -
jest -
jest-dom:@testing-library/jest-dom의 예전 이름- Jest를 사용하여 DOM 요소를 더 쉽게 테스트할 수 있게 하는 유틸리티
-
jest-environment-jsdom: Jest 테스트를 위한 JSDOM 환경- JSDOM은 실제 브라우저 없이 DOM 환경을 구현하는 도구입니다.
-
jest-plugin-context: Mocha 스타일의context헬퍼를 Jest에 추가하기 위한 플러그인 -
ts-jest: TypeScript를 사용하는 Jest 테스트를 변환하기 위한 도구- TypeScript로 작성된 테스트와 소스 코드를 Jest에서 직접 실행할 수 있게 해줌
Test Coverage
💡 Test Coverage
: 코드가 얼만큼 Test되고 있는지를 나타내는 Software 품질 지표
- 테스트 커버리지가 높은 소프트웨어는 버그가 발생할 확률이 적기 때문에 사용자가 좀 더 신뢰하고 사용할 수 있음
2. React Testing Library
💡 React Testing Library?
Behavior Driven Test(행위 주도 Test) 방법론이 대두되면서 함께 주목 받기 시작한 테스팅 라이브러리💡
Behavior Driven Test?: 사용자가 애플리케이션을 이용하는 관점에서 사용자의 실제 경험 위주로 Test 작성
- 사용자에게 어떤 컨텐츠가 현재 보이고, 사용자가 어떤 이벤트를 발생시켰을 때, 그에 따라 화면에 변화가 일어나는지 Test
- 기존에 관행했던
Implementation Driven Test(구현 주도 Test)의 단점을 보완하기 위한 방법론
Implementation Driven Test: 주로 애플리케이션이 어덯게 작동하는지에 대x해서 초점을 두어 Test 작성
Enzyme vs React Testing Library
Enzyme: Implementation Driven Test에 적합- 실제 브라우저 DOM이 아닌, Virtual DOM을 기준으로 Test 작성해야 함
- 테스트 대상 React 컴포넌트에 어떤 props이 넘어가고, 현재 state가 어떻게 되는지에 대해서 검증하기 용이
React Testing Library: Behavior Driven Test에 적합- JSDOM이라는 라이브러리를 통해 실제 브라우저 DOM을 기준으로 테스트를 작성
- 사용자 브라우저에서 렌더링하는 실제 HTML 마크업의 모습이 어떤지에 대해서 Test하기 용이
Decribe-Context-It 패턴
- Describe : 설명할 테스트 대상을 명시
- Context : 테스트 대상이 놓인 상황을 설명
- with나 when으로 시작
- it : 테스트 대상의 행동을 설명
- 장점
- 테스트 코드를 계층 구조로 만들어준다.
- 테스트 코드를 추가하거나 읽을 때 스코프 범위만 신경쓰면 된다.
- 재미 있다. 중독성이 있다.
-
예시
export const checkNull = (value?: string | null): string => { if (!value) { return ''; } return value; };describe('checkNull', () => { context('value가 null일 경우', () => { it('빈 문자열을 반환해야만 한다', () => { const result = checkNull(null); expect(result).toBe(''); }); }); context('value가 null이 아닌 경우', () => { it('입력된 값이 반환되어야만 한다', () => { const result = checkNull('nana'); expect(result).toBe('nana'); }); }); });
3. Cypress vs Jest + React Testing Library
Cypress
- E2E Test
- jest가 아닌 mocha사용
- mocha는 프레임워크가 아니라 라이브러리이기 때문에 다른 의존 모듈 필요
- 설치 직후 EC2 테스트 코드는 작성할 수 있지만, 컴포넌트 테스트를 하려면 추갖거인 의존 모듈, 플러그인, config 설정 필요
- 브라우저에서 hot reload모드 사용 가능
Jest + React Testing Library
-
Unit Test
-
CRA로 프로젝트를 시작하면 기본 설치
- Facebook에서 공식적으로 사용하라고 추천
-
Terminal에서 watch & hot reload모드 사용할 수 있음
💡 watch
: 파일 시스템의 변화를 감지하고, 그 변화가 일어날 때마다 특정 동작(예를 들면, 코드 컴파일 또는 테스트 실행)을 자동으로 수행
- JavaScript의 웹팩(Webpack)이나 타입스크립트(Typescript) 컴파일러 같은 도구들은 파일 변경을 감지하고 자동으로 코드를 재컴파일하는 watch 모드를 제공
💡 hot reload
: 코드나 리소스의 변화가 있을 때, 전체 앱을 다시 시작하는 대신 변경된 부분만을 빠르게 재로드하여 반영하는 방법
- 주로 프론트엔드 개발, 특히 웹 및 모바일 앱 개발에서 사용
- 작은 코드 변경사항을 즉시 확인할 수 있으므로 개발 프로세스가 더 빠르고 반응적이게
- e.g. Flutter와 React Native는 모바일 앱 개발에서 hot reload 기능을 제공하며, React와 같은 웹 프레임워크에서도 비슷한 기능을 제공
4. Frontend에서의 테스트 코드
- Frontend개발자에게도 테스트 코드가 필요할까?
어떤 기능을 개발하는지 관계없이 개발자는 개발을 마치고 요구사항에 맞게 잘 동작 하는지 테스트를 합니다. 테스트 코드를 작성해서 테스트하지 않더라도 직접 실행해보고 검증하는 과정은 꼭 필요합니다. 코드를 작성한 후 수동으로 한 땀 한 땀 테스트를 하려면 매번 기능의 가장 처음으로 되돌아가야 합니다. 버튼이나 링크가 있는 곳으로 이동해서 마우스로 클릭하고, 키보드로 입력하는 번거로운 과정을 거쳐야 하죠. 테스트를 코드로 작성하면 언제든 다시 실행할 수 있고, 원하는 기능 테스트를 자동으로 진행할 수 있습니다. 작성 안 할 이유가 없다고 생각합니다.
- 일정에 어떤 영향을 줄까요?
테스트 코드를 작성하는 건 일정에 도움이 될 수도 있고, 안될 수도 있습니다. 테스트를 코드로 작성하면 더는 매번 실행하고 확인하는 번거로운 과정을 생략하고 기능 개발에 집중할 수 있습니다. 목표 일정 내 기능을 개발하는 데 도움이 됩니다.
코드를 작성하고 직접 실행해서 내가 의도한 대로 작동하는지 확인하는 것은 쉽습니다. 하지만 테스트를 코드로 작성하는 건 생각보다 어렵습니다. 어쩌면 지금까지 코드를 작성한 시간만큼 테스트 코드를 작성하는데 시간을 써야 할지 모릅니다.
일정이 촉박한 상황이라면 테스트 코드 작성을 가장 먼저 포기하게 됩니다.
- 팀에게 진짜 도움이 되나요?
코드를 작성하는 것보다 읽는 것이 더 어렵다고 생각합니다. 내가 작성한 코드라도 오래된 코드를 다시 보면 왜 이렇게 작성했는지 파악하기 어렵습니다. 테스트 코드가 명세서의 역할을 하여 코드를 파악하는 데 도움을 줍니다.
또한 관련 코드를 개선할 때 테스트가 성공하는 것을 확인하면, 안심하고 개선할 수 있고, 잘못 수정한 것을 빠르게 알아차릴 수 있습니다.
반대로 테스트 코드가 작성되지 않은 부분을 개선해야 할 경우에는 더 조심스럽고 꼼꼼하게 확인하면서 진행해야 합니다. 여유가 된다면 테스트 코드를 작성하고 진행하는 것이 좋습니다.
- 테스트는 얼마나 작성하나요?
테스트 코드 작성에는 분명히 많은 시간과 노력이 필요합니다. 물론 모든 경우의 수를 테스트할 수 있게 테스트 코드를 작성하면 좋겠지만, 우리에게 주어진 시간과 자원은 무한하지 않습니다. 따라서 적절한 수준에서 올바르게 결함을 발견할 수 있는 수준으로 작성해야 합니다.
- 여러 모듈의 상호작용을 가장 높은 우선순위로 두고 통합 테스트 작성에 집중합니다.
- 복잡한 기능, 다른 팀원에게 부가 설명이 필요한 함수는 유닛테스트를 작성합니다. 테스트 코드가 함수의 기능을 설명하는 명세서의 역할도 합니다.
- 테스트 커버리지 퍼센트에 연연하지 않습니다. 1번에서 작성한 통합 테스트가 간접적으로 테스트하고, 추가로 확인할 필요 없으면 별도로 테스트를 작성하지 않아도 좋습니다.
Articles
Front-end에서의 Testing
프론트엔드 개발 흐름
- figma 등을 통해 본인이 맡은 페이지의 모습을 확인한다.
- 퍼블리셔 분이 만들어주신 컴포넌트를 일단 복붙하고 시작한다.
- 나름 관심사에 따라 한 페이지를 몇 개의 컴포넌트로 나눈다.
- 다시 각각을 container와 view로 구분한다.
- 비즈니스 로직을 어떻게든 구현한다.
- storybook을 통해 제대로 동작을 하는지 확인한다.
- 뭔가 꺼림칙하지만 이정도면 됐다 싶으니 commit 한다.
프론트엔드 테스트 해야할까? - (1)
프론트엔드도 반드시 테스트를 해야하는 이유
- 프론트엔드 관리, 프론트엔드 코드의 퀄리티의 중요성이 대두되면서 프론트엔드 테스팅도 같이 주목받기 시작했다.
- 테스트는 코드가 의도한대로 동작한다는 것을 보장한다.
- 처음 코드를 작성할 때보다 유지/보수 및 기능 수정 시 테스트는 더욱 큰 역할을 한다.
- 코드를 개선하기 위해 리팩토링하거나, 기능을 변경하기 위해 코드를 변경하는 경우가 더 많다.
- 테스트가 존재하지 않는다면 코드 수정 시 발생할 수 있는 사이드이팩트를 알 수 없다.
유닛테스트
: Unit단위 테스트 (단일 컴포넌트, 단일 서비스)
- 테스트 양이 가장 적어서 효유렂ㄱ이다.
- TDD사용 시 가장 작은 단위부터 기능대로 동작하는지 확인하면서 개발하기 좋다.
- 소프트웨어의 구조가 명확하지 않고, 단일 유닛의 기능이 자주 변경되는 환경에서는 테스트가 자주 변경되어야 하고 이는 테스트 작성의 효율을 떨어뜨리기 때문에 도전적인 프로젝트에 사용하는 것은 추천하지 않는다.
통합테스트
: 통합된 기능을 테스트(유닛들 간의 데이터를 주고받는 환경을 테스트)
- 프론트엔드 전체 유닛들만 통합하여 테스트하거나, 백엔드와 DB까지 전체를 통합해서 테스트하는 경우도 있다.
- 전체 유닛의 상호작용에 대해서 테스트가 가능하다
e2e테스트
: end to end의 약자로, SW 가장 끝단인 사용자로부터 가장 끝단인 백엔드 인프라까지 테스트하는 것을 의미한다.
- 사용자 관점엠서 전체 시나리오를 테스트하기에 코드의 변경에도 테스트를 변경할 필요가 없다.
- 시간이 오래 걸려 자주 테스트하기 어렵다.
- FE만 따로 테스트하는 것이 불가능하며, 백엔드와의 통합이 필요하다.
1. React Testing Library
: 리액트 컴포넌트를 가상으로 렌더링하고 동작을 확인하고 렌더링 결과를 확인할 수 있는 도구
-
공식 예제
// 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 () => { render(<Fetch url="/greeting" />) await userEvent.click(screen.getByText('Load Greeting')) expect(screen.getByRole('heading')).toHaveTextContent('hello there') }) -
유닛 테스트 및 통합테스트 시 사용하는 테스트 도구
Given When Then전략
- Given : 주어진 조건에 대한 코드
- When : 테스트할 행동에 대한 코드
- then : 테스트 검증 로직에 대한 코드
// given
const a = 1;
const b = 2;
// when
const result = plus(a,b);
// then
assert result === 3;