Jieunny์ ๋ธ๋ก๊ทธ
S4) Unit 7. [Testing] TDD ๋ณธ๋ฌธ
๐ฃ TDD
๐ญ. TDD(Test-driven Development)๋?
โ๏ธ ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ ์ ํ ์คํธ๋ฅผ ์ฐ๋ ์ํํธ์จ์ด ๊ฐ๋ฐ ๋ฐฉ๋ฒ๋ก
โฐ ๊ฐ๋ฐ์ ์์ ์ด ๋ฐ๋์งํ๋ค๊ณ ์๊ฐํ๋ ์ฝ๋์ ๊ฒฐ๊ณผ๋ฅผ ๋ฏธ๋ฆฌ ์ ์ํ๊ณ , ์ด๊ฒ์ ๋ฐํ์ผ๋ก ์ฝ๋๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ
โฐ ๋จ์์ ํ ์คํธ ์ผ์ด์ค๋ฅผ ์์ฑํ๊ณ , ์ด๋ฅผ ํต๊ณผํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ณผ์ ์ ๋ฐ๋ณตํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
๐ฎ. TDD์ ๊ฐ๋ฐ์ฃผ๊ธฐ
1๏ธโฃ Write Failing Test: ์คํจํ๋ ํ ์คํธ ์ฝ๋๋ฅผ ๋จผ์ ์์ฑํ๋ค.
2๏ธโฃ Make Test Pass: ํ ์คํธ ์ฝ๋๋ฅผ ์ฑ๊ณต์ํค๊ธฐ ์ํ ์ค์ ์ฝ๋๋ฅผ ์์ฑํ๋ค.
3๏ธโฃ Refactor: ์ค๋ณต ์ฝ๋ ์ ๊ฑฐ, ์ผ๋ฐํ ๋ฑ์ ๋ฆฌํฉํ ๋ง์ ์ํํ๋ค.
โฐ 1์ ๊ณผ์ ์ ๋ง์น๊ธฐ ์ ์ 2์ ์์ ์ ์์ํ์ง ์๋๋ก ์ฃผ์ํด์ผ ํ๋ค.
โฐ 2๋ฅผ ์งํํ ๋์๋, 1์ ํ ์คํธ๋ฅผ ํต๊ณผํ ์ ๋์ ์ต์ ์ฝ๋๋ง ์์ฑํด์ผ ํ๋ค.
๐ฏ. TDD๋ฅผ ์ฌ์ฉํ๋ ์ด์
โ๏ธ ์์ํ์ง ๋ชปํ๋ ๋ฒ๊ทธ๋ฅผ ์ค์ฌ ์์ ์๊ฐ์ ์ค์ผ ์ ์๋ค.
๐ฃ React ํ๊ฒฝ์์ ํ ์คํธํ๊ธฐ
โ๏ธ React์์ ํ ์คํธ๋ Testing Library, Jest๋ฅผ ์ด์ฉํด์ ํ ์ ์๋ค.
โฐ create-react-app์ ์ด์ฉํ์ฌ React ํ๋ก์ ํธ๋ฅผ ์์ฑํ๋ฉด ์๋์ผ๋ก Testing Libarary๋ฅผ ์ด์ฉํ ์ ์๋ค.
ใด Testing Library๋ ํ ์คํธ๋ฅผ ์คํํ๊ณ ์ถ์ ์ปดํฌ๋ํธ๋ ํด๋ฆญ ์ด๋ฒคํธ ๋ฑ์ ์ฌ์ฉํ ์ ์๋ค.
โฐ Jest : JavaScript์ Testing Framework / Test Runner๋ก์จ, ํ ์คํธ ํ์ผ์ ์๋์ผ๋ก ์ฐพ์ ํ ์คํธ๋ฅผ ์คํํ๊ณ , ํ ์คํธ๋ฅผ ์คํํ ๊ฒฐ๊ณผ ๊ธฐ๋๋งํผ ์ฌ๋ฐ๋ฅธ ๊ฐ์ ๊ฐ์ง๊ณ ์๋์ง ํจ์๋ฅผ ์ด์ฉํ์ฌ ์ฒดํฌํ์ฌ ํ ์คํธ๊ฐ ์ฑ๊ณต์ธ์ง ์คํจ์ธ์ง๋ฅผ ํ๋จํด์ค๋ค.
๐ญ. React ๊ธฐ๋ณธ ํ ์คํธ ํ๊ฒฝ ํ์ธํ๊ธฐ
1-1) ์๋ก์ด React ํ๋ก์ ํธ ์์ฑํ๊ธฐ
โฐ package.json ํ์ผ์ ํ์ธํ๋ฉด dependencies ์์ @testing์ด๋ผ๋ ์ ๋์ด๊ฐ ๋ถ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ํ์ธํ ์ ์๋ค.
ใด @testing-library/jest-dom : jest-dom์์ ์ ๊ณตํ๋ custom matcher๋ฅผ ์ฌ์ฉํ ์ ์๊ฒ ํด์ค๋๋ค.
ใด @testing-library/react : ์ปดํฌ๋ํธ์ ์์๋ฅผ ์ฐพ๊ธฐ ์ํ query๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
ใด @testing-library/user-event : click ๋ฑ ์ฌ์ฉ์ ์ด๋ฒคํธ์ ์ด์ฉ๋ฉ๋๋ค.
1-2) ํ ์คํธ ํ์ผ ํ์ธํ๊ธฐ
โฐ ๋ค์ src ํด๋ ์์ ํ์ธํด ๋ณด๋ฉด, setupTests.js ์ App.test.js ๋ผ๋ ์ด๋ฆ์ ํ์ผ์ ํ์ธํ ์ ์๊ณ , App.test.js ํ์ผ์๋ ๊ฐ๋จํ ํ ์คํธ๊ฐ ์ด๋ฏธ ๋ง๋ค์ด์ ธ ์๋ค.
โฐ test ํจ์๋ Jest ํจ์๋ก ํ ์คํธ๋ฅผ ์คํํ ๋ ๋ฐ๋์ ์ด์ฉํ๋ ํจ์๋ก, ์ฒซ ๋ฒ์งธ ์ธ์๋ ํ ์คํธ๊ฐ ์ด๋ค ๋ด์ฉ์ธ์ง ๋์ค์ ๋ค์ ์ฝ์ด๋ ํ ์คํธ ๋ด์ฉ์ ์ ์ ์๋ ์ค๋ช ์ ์์ฑํ๊ณ ๋ ๋ฒ์งธ ์ธ์๋ ํ๊ณ ์ ํ๋ ํ ์คํธ๋ฅผ ํจ์์ ํํ๋ก ๋ฃ๋๋ค.
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
// ํ
์คํธํ๊ณ ์ ํ๋ ์ปดํฌ๋ํธ๋ฅผ render()ํจ์๋ก ์ ๋ฌ
// eact-testing-library์์๋ ํ
์คํธ๋ฅผ ์งํํ ์ปดํฌ๋ํธ๋ฅผ render()ํจ์์ ์ธ์๋ก ์ ๋ฌํ๋ค.
const linkElement = screen.getByText(/learn react/i);
// ender()์์ ๊ฐ์ ธ์จ App ์ปดํฌ๋ํธ ์ค "learn react"๋ผ๋ ๋ฌธ์์ด์ด ์๋์ง ํ์ธํ์ฌ linkElement์ ํ ๋นํ๋ค.
expect(linkElement).toBeInTheDocument();
// expect ํจ์์ ์ธ์๋ก ์ง์ ํ ์์๊ฐ document.body์ ์กด์ฌํ๋์ง toBeInTheDocument ํจ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฒดํฌํ๋ค.
// toBeInTheDocument ํจ์๋ matchers ํจ์๋ผ๊ณ ๋ถ๋ฅธ๋ค.
});
โฐ test ํจ์, expect ํจ์๋ Jest์ ํจ์๊ณ , toBeInTheDocument๋ jest-dom ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํฌํจ๋ Custom matchers์ด๋ค.
โฐ ์์ง jest-dom์ import ํ์ง ์์๋๋ฐ๋ toBeInTheDocument๋ฅผ ์ฌ์ฉํ ์ ์๋ ์ด์ ๋ src ํด๋์ setupTests.js ํ์ผ ๋ด์์ import ๋๊ณ ์๊ธฐ ๋๋ฌธ์ด๋ค.
1-3) ๊ฐ๋จํ ํ ์คํธ ์ง์ ๋ง๋ค๊ธฐ
โฐ ํ์ผ๋ช ์ <ํ์ผ๋ช >.test.js๋ก ํ๊ณ ํ ์คํธ๋ฅผ ์์ฑํ๋ฉด, Jest๊ฐ ํ ์คํธ ํ์ผ๋ก ํ๋จํด์ ์๋ํ๋ค.
โฐ ๋ง์ ์ ๊ฐ๋จํ ํ ์คํธ
test('2 ๋ํ๊ธฐ 2๋ 4', () => {
expect(2 + 2).toBe(4);
});
// toBe ํจ์๋ matchers ํจ์ ์ค ํ๋๋ก expect ํจ์์ ์ง์ ํ ๊ฐ์ด toBe ํจ์์ ์ง์ ํ ๊ฐ๊ณผ ์ผ์นํ๋์ง ์ฒดํฌํ๋ค.
โฐ npm run test๋ฅผ ์คํํ์ฌ "a"๋ฅผ ์ ํํ๋ฉด Example.test.js์ App.test.js๊ฐ ๋ชจ๋ ์คํ์ด ๋๋ค.
describe('๊ฐ๋จํ ํ
์คํธ๋ค', () => {
test('2 ๋ํ๊ธฐ 2๋ 4', () => {
expect(2 + 2).toBe(4);
});
text('2 ๋นผ๊ธฐ 1์ 1', () => {
expect(2 - 1).toBe(1);
});
});
// test ํจ์ ๋์ it ํจ์๋ฅผ ์ฌ์ฉํด๋ ๊ฐ์ ๊ฒฐ๊ณผ๊ฐ ๋์ค๋ฉฐ
// describeํจ์๋ฅผ ์ฌ์ฉํ๋ฉด itํจ์๋ testํจ์๋ฅผ ํ๋์ ํ์ผ์ ์ฌ๋ฌ ๊ฐ ํฌํจํ ์ ์๋ค.
โฐ describe ํจ์ ๋ธ๋ก์ Test Suites๋ผ๊ณ ๋ถ๋ฆฌ๋ฉฐ test/it ํจ์ ๋ธ๋ก์ Test(Test Case)๋ผ๊ณ ํ๋ค.
๐ฎ. ์ง์ ๋ง๋ ์ปดํฌ๋ํธ ํ ์คํธํ๊ธฐ
2-1) ์ปดํฌ๋ํธ ๋ง๋ค๊ธฐ
import {useState} from 'react';
function Light({ name }) {
const [light, setLight] = useState(false);
return (
<div>
<h1>
{name} {light ? 'ON' : 'OFF'}{' '}
</h1>
<button
onClick={() => setLight(true)}
disabled={light ? true : false}
>
ON
</button>
<button
onClick={() => setLight(false)}
disabled={!light ? true : false}
>
OFF
</button>
</div>
);
}
export default Light;
// ์ ์์ ์ํ๋ฅผ OFF์์ ON์ผ๋ก ์ ํํ๋ ์ปดํฌ๋ํธ
2-2) ์ปดํฌ๋ํธ ํ ์คํธํ๊ธฐ
// Light.test.js
import { render, screen } from '@testing-library/react';
import Light from './Light';
it('renders Light Component', () => {
render(<Light name="์ ์" />);
const nameElement = screen.getByText(/์ ์ off/i);
// props๋ก ์ ๋ฌ๋ ์ ์์ด ์ฌ๋ฐ๋ฅด๊ฒ ํ์๋์ด ์๋์ง ํ์ธ
expect(nameElement).toBeInTheDocument();
})
------------------------------------------------------------------------
it('off button disabled', () => {
render(<Light name="์ ์" />);
const offButtonElement = screen.getByRole('button', { name: 'OFF' });
expect(offButtonElement).toBeDisabled();
// ํ์ฌ OFF๋ฒํผ์ด disabled ์ํ๋ผ๋ ๊ฒ์ ํ
์คํธ ํ๊ธฐ
})
------------------------------------------------------------------------
it('on button enable', () => {
render(<Light name="์ ์" />);
const onButtonElement = screen.getByRole('button', { name: 'ON' });
expect(onButtonElement).not.toBeDisabled();
// ON ๋ฒํผ์ด disabled๊ฐ ์๋๋ผ๋ ๊ฒ์ ํ
์คํธ ํ๊ธฐ
});
------------------------------------------------------------------------
import { fireEvent, render, screen } from '@testing-library/react';
import Light from './Light';
it('change from off to on', () => {
render(<Light name="์ ์" />);
const onButtonElement = screen.getByRole('button', { name: 'ON' });
fireEvent.click(onButtonElement);
// ๋ฒํผ ํด๋ฆญ ์ด๋ฒคํธ์ ์ ๋ฌด ํ
์คํธ ํ๊ธฐ
// fireEvent์ click๋ฉ์๋์ ์ ๋ฌ์ธ์๋ก ํ
์คํธํ๊ณ ์ ํ๋ ์์๋ฅผ ์ ๋ฌ
expect(onButtonElement).toBeDisabled();
})
'CodeStates > learning contents' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
S4) Unit 8. [์ต์ ํ]์บ์ & Tree Shaking & Lighthouse (0) | 2023.03.30 |
---|---|
S4) Unit 8. [์ต์ ํ]์ต์ ํ ๊ธฐ๋ฒ (0) | 2023.03.30 |
S4) Unit 6. [API] GraphQL (0) | 2023.03.28 |
S4) Unit 5. [์ปดํจํฐ ๊ณตํ] ์ด์์ฒด์ & ๋ฌธ์์ด๊ณผ ๊ทธ๋ํฝ & ๊ฐ๋น์ง ์ปฌ๋ ์ (1) | 2023.03.27 |
S4) Unit 5. [์ปดํจํฐ ๊ณตํ] ์ปดํจํฐ ๊ตฌ์กฐ (2) | 2023.03.27 |