TDD
왜 TDD를 해야 할까?
- 테스트 코드가 없는 프로젝트의 경우 무엇을 개발해야 하는지 명확히 모른채 개발을 하게 되며 추후 코드 변경에 따른 버그를 예측할 수 없다.
TDD란?
TDD는 문제를 먼저 정의한 후 문제의 해답을 찾아가는 방식이다. 실패(Red), 성공(Green), 리팩토링(Refactoring) 이라 불리는 3단계를 반복한다.
- 실패: 문제 정의 단계로, 테스트 코드를 실제 코드보다 먼저 작성
- 성공: 문제 해결 단계로, 테스트를 통과할 수 있는 최소한의 코드를 작성
- 리팩토링: 불필요하고 비효율 적인 코드를 개선
- 문제 정의를 위한 Tip (뱅크샐러드 - 테크 스펙)
- 뱅크샐러드의 특별한 스펙, '테크 스펙' | 뱅크샐러드
테스트 범주
테스트 코드는 내가 만든 기능의 주요 핵심이 무엇인지 파악하고, 그 핵심에 집중해 테스트 코드를 작성하는 것이 좋다.
예를 들면, util 성격의 라이브러리인 lodash가 잘 작동하는지 확인하거나, useEffect가 mount 된 직후 호출되는지 굳이 테스트할 필요가 없다. lodash를 사용한 메서드나, useEffect안의 로직이 잘 동작하는지 확인하면 된다.
이러한 관점에서 2가지 부분으로 테스트 범주를 생각할 수 있다.
1) Business logic
서비스의 규모나 상황에 따라 각기 다른 구조로 구현한다.
2) Components
사용자에게 직접적으로 보여지는 부분이 중요하게 테스트 되어야 한다.
- Props가 잘 받아와 졌는가?
- State가 의도한 대로 잘 관리되고 있는가?
- Props나 State를 토대로 Component가 잘 rendering 되고 있는가?
- event handler가 잘 동작 하는가?
- lifecycle에 맞게 동작하는가?
React Testing Library
Jest 기반으로 만들어졌으며, 사용자가 컴포넌트를 사용하는 것처럼 테스트를 작성할 수 있다.
react-testing-library에서 컴포넌트를 렌더링 할 때에는 render()함수를 사용된다. 해당 함수에 render하고자 하는 component를 넣어주면, DOM을 선택할 수 있는 쿼리들과 container가 반환된다. 그 내부 쿼리 함수로 getByText가 존재하며 해당 함수를 사용하면 Text를 사용하여 원하는 dom을 선택 할 수 있다.
- describe메서드를 사용해 무엇을 테스트 할 것인지 기술하는 Test suite를 작성한다.
- it메서드로 실제 assertion하려는 테스트 케이스를 작성한다.
- expect를 이용해서 assertion의 결과를 확인한다.
npm install --save-dev @testing-library/react
테스트코드 작성하기
- 실패 코드 작성하기
-의도하고자 했던 기능들을 테스트 코드로 먼저 작성한다.
describe('label이 존재하는 input componrnt', () => {
it('label props에 "아이디"를 넣으면 label 영역에 아이디가 render 되어야 한다.', () => {
const LABEL = '아이디';
const { getByText } = render(<Input label={ LABEL } />);
const labelElement = getByText(new RegExp(LABEL, 'i'));
expect(labelElement).toBeInTheDocument();
})
.. 그외 필요하다 생각되는 테스트 코드들 추가
})
- 성공 코드 작성하기
import React from 'react';
interface Props {
label: string;
}
export const Input = ({ label }: Props) => (
<div>
<label>{ label }</label>
</div>
);
- ㅈ리팩토링
- 1~3 반복
카운트 테스트 예시
test('check init count', () => {
const { getByText } = render(<Counter />);
// +버튼 클릭
const plusElement = getByText( '+' );
fireEvent.click( plusElement );
// 1이 있는지
const countElement = getByText( '1' );
expect( countElement ).toBeInTheDocument();
// -버튼 클릭
const minusElement = getByText( '-' );
fireEvent.click( minusElement );
// 0이 있는지
const countElement2 = getByText( '0' );
expect( countElement2 ).toBeInTheDocument();
});
----
좋은 테스트 원칙
React Test 정리
Cypress 예시
/// <reference types="cypress" />
import '@testing-library/cypress/add-commands';
import 'cypress-react-selector';
describe('Match Introduce', () => {
beforeEach(() => {
cy.visit('/');
cy.viewport(360, 720);
});
it('/ 접속시 /match/introduce 접속', () => {
cy.url().should('include', '/match/introduce');
});
it('팝업 이벤츠창이 뜨는지 확인', () => {
cy.findByText('오늘은 그만 보기').should('exist');
});
it('닫기 버튼 클릭시 popup 제거', () => {
cy.get('a > img').should('exist');
cy.findByText('닫기').click();
cy.get('a > img').should('not.exist');
});
it('잡다매칭 시작하기 버튼 클릭시 position으로 이동', () => {
cy.findByText('닫기').click();
cy.findByText('잡다매칭 시작하기').click();
cy.url().should('include', '/match/position');
});
it('FAQ 링크 클릭시 notion 설명하기 이동', () => {
cy.findByText('닫기').click();
cy.get('.notion-link').click().as('notionClick');
cy.wait(100);
cy.url().should('include', 'dour-sweater-a18.notion.site');
});
});
참고 레퍼런스
'Web > React' 카테고리의 다른 글
React Hook Form 가이드 (0) | 2022.08.02 |
---|---|
React에서 Styled Component 사용법 (0) | 2022.04.22 |
React 상태관리 Mobx 정리 및 사용법 (0) | 2022.04.22 |
React 프로젝트 Ionic Capacitor 사용하여 Android, Ios Build하기 (0) | 2022.04.22 |
React Query와 상태관리 (0) | 2022.03.30 |