TDD

๊น€๋‚จ๊ฒฝยท2023๋…„ 1์›” 31์ผ

react

๋ชฉ๋ก ๋ณด๊ธฐ
36/37

์˜๋ฏธ

๐Ÿ’ก 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์˜ ์‚ฌ์šฉ

Mocha

ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ๋•Œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ์‹คํŒจ.
์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์œผ๋ฉด, ์‹คํŒจํ•˜์ง€ ์•Š์Œ

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(ํ•จ์ˆ˜("ํ…Œ์ŠคํŠธ ๋‚ด์šฉ") === "์ •๋‹ต");
  });

});

Chai

ํ…Œ์ŠคํŠธ์— ํ•„์š”ํ•œ ํ—ฌํผ ํ•จ์ˆ˜๋“ค์ด ๋‹ด๊ธด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

//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 ๋‘˜ ์ค‘์— ํ•œ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์„ ํƒ

Jest

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();
   })
});


profile
๊ธฐ๋ณธ์— ์ถฉ์‹คํ•˜๋ฉฐ ์•ž์œผ๋กœ ๋ฐœ์ „ํ•˜๋Š”

0๊ฐœ์˜ ๋Œ“๊ธ€