๐ก Test-driven Development
๐ก ์ฝ๋๋ฅผ ์์ฑํ๊ธฐ ์ ์ ํ
์คํธ๋ฅผ ์ฐ๋ ์ํํธ์จ์ด ๊ฐ๋ฐ ๋ฐฉ๋ฒ๋ก
๐ก ์์ ๋จ์์ ํ
์คํธ ์ผ์ด์ค๋ฅผ ์์ฑํ๊ณ , ์ด๋ฅผ ํต๊ณผํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ณผ์ ์ ๋ฐ๋ณตํ๋ ๊ฒ
Write Failing Test ์คํจํ๋ ํ ์คํธ ์ฝ๋ ์์ฑ
Make Test Pass ํ ์คํธ ์ฝ๋๋ฅผ ์ฑ๊ณต์ํค๊ธฐ ์ํ ์ค์ ์ฝ๋๋ฅผ ์์ฑ
Refactor ์ค๋ณต ์ฝ๋ ์ ๊ฑฐ, ์ผ๋ฐํ ๋ฑ์ ๋ฆฌํฉํ ๋ง
โ
Write Failing Test์ ๊ณผ์ ์ ๋ง์น๊ธฐ ์ ์ Make Test Pass์ ์์
์ ์์ํ์ง ์๋๋ก
โ
Make Test Pass๋ฅผ ์งํํ ๋์๋, Write Failing Test์ ํ
์คํธ๋ฅผ ํต๊ณผํ ์ ๋์ ์ต์ ์ฝ๋๋ง ์์ฑ
๐ก ์์ํ์ง ๋ชปํ๋ ๋ฒ๊ทธ๋ฅผ ์ค์ฌ ์์ ์๊ฐ์ ์ค์ผ ์ ์๊ธฐ ๋๋ฌธ
๐ก mocha, chai, jest ๋ฑ
๐ก JavaScript ๋ด์ฅ ๊ธฐ๋ฅ์ด ์๋๋ผ ํ
์คํธ ํ๋ ์์ํฌ์์ ์ ๊ณตํ๋ ํ
์คํธ ์์ฑ์ ์ํ ๋๊ตฌ์ธ describe, it, assert, expect์ ์ฌ์ฉ
ํจ์๋ฅผ ์คํํ ๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด, ์คํจ.
์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด, ์คํจํ์ง ์์
describe("ํ
์คํธ ๋ํญ๋ชฉ", function () {
it("์ค๋ฅ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉด, ์คํจํ์ง ์์", function () {
let even = function (num) {
return num % 2 === 0;
};
return even(10) === true;
});
it("์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด, ์คํจ", function () {
let even = function (num) {
return num / 2 === 0;
};
if (even(10) !== true) {
throw new Error("์๋ฌ!");
}
});
});
๐ if/throw ๊ตฌ๋ฌธ์ผ๋ก ์ค๋ฅ ์ฒดํฌ
describe("ํ
์คํธ ๋ํญ๋ชฉ", function () {
it("์ค๋ฅ ์ฒดํฌ ํญ๋ชฉ", function () {
if (ํจ์("ํ
์คํธ ๋ด์ฉ") !== "์ ๋ต") {
throw new Error("Test failed");
}
});
});
๐ ์ ๋ ฅ๊ฐ์ด true๊ฐ ์๋ ๊ฒฝ์ฐ ์๋ฌ๋ฅผ ๋ฐ์
describe("ํ
์คํธ ๋ํญ๋ชฉ", function () {
let assert = function (isTrue) {
if (!isTrue) {
throw new Error("Test failed");
}
};
it("์ค๋ฅ ์ฒดํฌ ํญ๋ชฉ", function () {
assert(ํจ์("ํ
์คํธ ๋ด์ฉ") === "์ ๋ต");
});
});
ํ ์คํธ์ ํ์ํ ํฌํผ ํจ์๋ค์ด ๋ด๊ธด ๋ผ์ด๋ธ๋ฌ๋ฆฌ
//index.test.js
global.chai = require("chai");
global.ํจ์ = require("../ํจ์.js");
describe("ํ
์คํธ ํฐ ํญ๋ชฉ", function() {
require("../ํจ์.test.js");
});
๐ assert
describe("ํ
์คํธ ๋ํญ๋ชฉ", function () {
let assert = chai.assert;
it("ํ
์คํธ ์ํญ๋ชฉ", function () {
assert(ํจ์("ํ
์คํธ ๋ด์ฉ") === "์ ๋ต");
});
});
๐ expect
describe("ํ
์คํธ ๋ํญ๋ชฉ", function () {
let expect = chai.expect;
it("ํ
์คํธ ์ํญ๋ชฉ", function () {
expect(ํจ์("ํ
์คํธ ๋ด์ฉ")).to.equal("์ ๋ต");
});
});
๐ should
describe("ํ
์คํธ ๋ํญ๋ชฉ", function () {
let should = chai.should();
it("ํ
์คํธ ์ํญ๋ชฉ", function () {
should.equal(ํจ์("ํ
์คํธ ๋ด์ฉ"), "์ ๋ต");
});
});
โ expect๋ should ๋ ์ค์ ํ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ ํ
npx create-react-app ์คํํ๋ฉด package.json์์ ํ์ธ ๊ฐ๋ฅ
{
//...
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
//...
}
โ
@testing-library/jest-dom Jest-dom ์ ๊ณตํ๋ custom matcher ์ฌ์ฉ ๊ฐ๋ฅ
โ
@testing-library/react ์ปดํฌ๋ํธ์ ์์๋ฅผ ์ฐพ๊ธฐ ์ํ query ํฌํจ
โ
@testing-library/user-event click ๋ฑ ์ฌ์ฉ์ ์ด๋ฒคํธ์ ์ด์ฉ
๐ ๊ธฐ๋ณธ ๋ด์ฅ ํ ์คํธ
//src/setupTests.js
import '@testing-library/jest-dom';
//App.test.js
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
//ํ
์คํธํ๊ณ ์ ํ๋ ์ปดํฌ๋ํธ๋ฅผ render()ํจ์๋ก ์ ๋ฌ
render(<App />);
//App ์ปดํฌ๋ํธ ์ค "learn react"๋ผ๋ ๋ฌธ์์ด์ด ์๋์ง ํ์ธ
//i๋ก ๋์๋ฌธ์๋ฅผ ๊ฐ๋ฆฌ์ง ์์
const linkElement = screen.getByText(/learn react/i);
//toBeInTheDocument ํจ์๋ matchers ํจ์
expect(linkElement).toBeInTheDocument();
});
โ
test ํจ์, expect ํจ์๋ Jest์ ํจ์
โ
toBeInTheDocument๋ jest-dom ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํฌํจ๋ Custom matchers
๐ ๊ธฐ๋ณธ ํ ์คํธ
describe("๊ฐ๋จํ ํ
์คํธ๋ค", () => {
test("2 ๋ํ๊ธฐ 2๋ 4", () => {
expect(2 + 2).toBe(4);
});
//it๋ ๊ฐ๋ฅ
it("2 ๋นผ๊ธฐ 1์ 1", () => {
expect(2 - 1).toBe(1);
});
});
โ
toBe ํจ์๋ matchers ํจ์ ์ค ํ๋
๐ ์ปดํฌ๋ํธ ํ ์คํธ
//Light.js
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;
//Light.test.js
import { fireEvent, render, screen } from '@testing-library/react';
import Light from './Light';
describe("Light Component tests", () => {
it('renders Light Component', () => {
render(<Light name="์ ์" />);
const nameElement = screen.getByText(/์ ์ off/i);
expect(nameElement).toBeInTheDocument();
})
it('on button enable', () => {
render(<Light name="์ ์" />);
const onButtonElement = screen.getByRole('button', { name: 'ON' });
//disabled๊ฐ ์๋
expect(onButtonElement).not.toBeDisabled();
});
it('change from off to on', () => {
render(<Light name="์ ์" />);
const onButtonElement = screen.getByRole('button', { name: 'ON' });
//fireEvent : ๋ฒํผ ํด๋ฆญ ์ด๋ฒคํธ์ ์ ๋ฌด
fireEvent.click(onButtonElement);
expect(onButtonElement).toBeDisabled();
})
});