์ƒ์„ธ ์ปจํ…์ธ 

๋ณธ๋ฌธ ์ œ๋ชฉ

[๋…ธ๋“œ 2] 11์žฅ. ๋…ธ๋“œ ์„œ๋น„์Šค ํ…Œ์ŠคํŠธํ•˜๊ธฐ

23-24/Node.js 2

by _๋„๋‹ด 2023. 12. 29. 10:00

๋ณธ๋ฌธ

728x90

 

๐ŸŒŸ11์žฅ ํ‚ค์›Œ๋“œ๐ŸŒŸ

jest ํŒจํ‚ค์ง€

์œ ๋‹› ํ…Œ์ŠคํŠธ

ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ

 

 

1. ํ…Œ์ŠคํŠธ ์ค€๋น„

(9์žฅ์˜ NodeBird ์„œ๋น„์Šค๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ)

ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ jest ํŒจํ‚ค์ง€ ์„ค์น˜ npm i -D jest

 

package.json์— ๋ช…๋ น์–ด ์ถ”๊ฐ€

"scripts": {
    "start": "nodemon app",
    "test": "jest"
  },

middlewares/index.test.js ์— ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑ ํ›„
npm test๋กœ ํ…Œ์ŠคํŠธ ์‹คํ–‰

 

2. ์œ ๋‹› ํ…Œ์ŠคํŠธ

์‹ค์ œ NodeBird์˜ ์ฝ”๋“œ๋ฅผ ํ…Œ์ŠคํŠธํ•ด ๋ณด๊ธฐ
middlewares/index.js์— ์žˆ๋Š” isLoggedIn, isNotLoggedIn ํ•จ์ˆ˜๋ฅผ ํ…Œ์ŠคํŠธ

 

https://inpa.tistory.com/entry/JEST-%F0%9F%93%9A-%EB%AA%A8%ED%82%B9-mocking-jestfn-jestspyOn

 

[JEST] ๐Ÿ“š ๋ชจํ‚น Mocking ์ •๋ฆฌ - jest.fn / jest.mock /jest.spyOn

Mocking ์›๋ฆฌ mocking์ด๋ž€ (mock = ๋ชจ์กฐํ’ˆ) ๋œป ๊ทธ๋Œ€๋กœ ๋ฐ›์•„๋“œ๋ฆฌ๋ฉด ๋œ๋‹ค. ์ฆ‰ ํ…Œ์ŠคํŠธํ•˜๊ณ ์ž ํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์˜์กดํ•˜๋Š” function์ด๋‚˜ class์— ๋Œ€ํ•ด ๋ชจ์กฐํ’ˆ์„ ๋งŒ๋“ค์–ด '์ผ๋‹จ' ๋Œ์•„๊ฐ€๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค. ํ•œ๋งˆ๋””๋กœ, ๋‹จ์œ„

inpa.tistory.com

๐Ÿ“Œ ๋ชจํ‚น

  • ๊ฐ€์งœ ๊ฐ์ฒด, ๊ฐ€์งœ ํ•จ์ˆ˜๋ฅผ ๋„ฃ๋Š” ํ–‰์œ„ jest.fn()
  • ๊ฐ€์งœ ๋ชจ๋“ˆ๋„ ๋ชจํ‚น ๊ฐ€๋Šฅ jest.mock() ๋ฉ”์„œ๋“œ - ๋ชจํ‚นํ•  ๋ชจ๋“ˆ์˜ ๊ฒฝ๋กœ๋ฅผ ์ธ์ˆ˜๋กœ ๋„ฃ๊ณ  ๊ทธ ๋ชจ๋“ˆ์„ ๋ถˆ๋Ÿฌ์˜ด, ํ•ด๋‹น ๋ชจ๋“ˆ ๋‚ด์˜ ๋ชจ๋“  ๋ฉ”์„œ๋“œ๊ฐ€ ๊ฐ€์งœ ๋ฉ”์„œ๋“œ๊ฐ€ ๋จ, ๊ฐ€์งœ ๋ฉ”์„œ๋“œ์— ๊ฐ€์งœ ๋ฐ˜ํ™˜๊ฐ’ ์ง€์ • ๊ฐ€๋Šฅ
  • ๋ชจ๋“ˆ ๋ชจํ‚น ์˜ˆ์‹œ: User ๋ชจ๋ธ์€ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ๋Š” ์‚ฌ์šฉ ๋ถˆ๊ฐ€

controllers/user.test.js ํŒŒ์ผ

jest.mock('../models/user');
const User = require('../models/user');
const { follow } = require('./user');

describe('follow', () => {
  const req = {
    user: { id: 1 },
    params: { id: 2 },
  };
  const res = {
    status: jest.fn(() => res),
    send: jest.fn(),
  };
  const next = jest.fn();

  test('์‚ฌ์šฉ์ž๋ฅผ ์ฐพ์•„ ํŒ”๋กœ์ž‰์„ ์ถ”๊ฐ€ํ•˜๊ณ  success๋ฅผ ์‘๋‹ตํ•ด์•ผ ํ•จ', async () => {
    User.findOne.mockReturnValue({
      addFollowing(id) {
        return Promise.resolve(true);
      }
    });
    await follow(req, res, next);
    expect(res.send).toBeCalledWith('success');
  });

  test('์‚ฌ์šฉ์ž๋ฅผ ๋ชป ์ฐพ์œผ๋ฉด res.status(404).send(no user)๋ฅผ ํ˜ธ์ถœํ•จ', async () => {
    User.findOne.mockReturnValue(null);
    await follow(req, res, next);
    expect(res.status).toBeCalledWith(404);
    expect(res.send).toBeCalledWith('no user');
  });

  test('DB์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด next(error) ํ˜ธ์ถœํ•จ', async () => {
    const message = 'DB์—๋Ÿฌ';
    User.findOne.mockReturnValue(Promise.reject(message));
    await follow(req, res, next);
    expect(next).toBeCalledWith(message);
  });
});

์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋‚ด์šฉ์„ ๋“ฑ๋กํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ฏ€๋กœ ์‹ค์ œ ์„œ๋น„์Šค์˜ ์‹ค์ œ DB์—์„œ๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ.

์œ ๋‹› ํ…Œ์ŠคํŠธ๋กœ๋Š” ํ•œ๊ณ„๊ฐ€ ์žˆ์Œ => ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋‚˜ ์‹œ์Šคํ…œ ํ…Œ์ŠคํŠธ

 

3. ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€

  • ์ „์ฒด ์ฝ”๋“œ ์ค‘์—์„œ ํ…Œ์ŠคํŠธ๋˜๊ณ  ์žˆ๋Š” ์ฝ”๋“œ์˜ ๋น„์œจ๊ณผ ํ…Œ์ŠคํŠธ๋˜๊ณ  ์žˆ์ง€ ์•Š์€ ์ฝ”๋“œ์˜ ์œ„์น˜๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ๊ธฐ๋Šฅ (jest์— ํฌํ•จ)

package.json์— ๋ช…๋ น ์ถ”๊ฐ€
jest --coverage ์˜ต์…˜: jest๊ฐ€ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ๋ถ„์„

"scripts": {
    "start": "nodemon app",
    "test": "jest",
    "coverage": "jest --coverage"
  },

File: ํŒŒ์ผ๊ณผ ํด๋” ์ด๋ฆ„
% Stmt: ๊ตฌ๋ฌธ ๋น„์œจ
% Branch: if๋ฌธ ๋“ฑ์˜ ๋ถ„๊ธฐ์  ๋น„์œจ
% Funcs: ํ•จ์ˆ˜ ๋น„์œจ
% Lines: ์ฝ”๋“œ ์ค„ ์ˆ˜ ๋น„์œจ
Uncovered Lines #s: ์ปค๋ฒ„๋˜์ง€ ์•Š์€ ์ค„ ์œ„์น˜

models/user.test.js ์ž‘์„ฑ

 

4. ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ

  • ํ•˜๋‚˜์˜ ๋ผ์šฐํ„ฐ๋ฅผ ํ†ต์งธ๋กœ ํ…Œ์ŠคํŠธํ•ด ๋ณด๊ธฐ -> routes/auth.test.js ํŒŒ์ผ ์ž‘์„ฑ
  • ํ•˜๋‚˜์˜ ๋ผ์šฐํ„ฐ์—๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ๋ถ™์–ด ์žˆ๊ณ  ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์‚ฌ์šฉ๋จ. ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ์—์„œ๋Š” ๊ทธ ๋ชจ๋“  ๊ฒƒ๋“ค์ด ์œ ๊ธฐ์ ์œผ๋กœ ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•œ๋‹ค.

ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ ์ค€๋น„

  • npm i -D supertest ํŒจํ‚ค์ง€ ์„ค์น˜
  • routes/auth.test.js ํŒŒ์ผ ์ž‘์„ฑ
  • supertest ๋ชจ๋“ˆ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜ํ•  ์ : app ๊ฐ์ฒด๋ฅผ ๋ชจ๋“ˆ๋กœ ๋งŒ๋“ค์–ด ๋ถ„๋ฆฌ, server.js์—์„œ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ตฌ์กฐ๋กœ ๋งŒ๋“ค๊ธฐ, npm start ๋ช…๋ น๋„ ํŒŒ์ผ ์ด๋ฆ„์— ๋งž๊ฒŒ ๋ณ€๊ฒฝ (nodemon server)
  • ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •: config/config.json์—์„œ test ์†์„ฑ์„ ์ˆ˜์ •
  • npx sequelize db:create --env test ํ…Œ์ŠคํŠธ์šฉ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ƒ์„ฑ ๋ช…๋ น

routes/auth.test.js ์ž‘์„ฑ - ๋กœ๊ทธ์ธ ๋ผ์šฐํ„ฐ ํ…Œ์ŠคํŠธ

const request = require('supertest');
const { sequelize } = require('../models');
const app = require('../app');

beforeAll(async () => {
  await sequelize.sync();
});

describe('POST /join', () => {
  test('๋กœ๊ทธ์ธ ์•ˆ ํ–ˆ์œผ๋ฉด ๊ฐ€์ž…', (done) => {
    request(app)
      .post('/auth/join')
      .send({
        email: 'zerohch0@gmail.com',
        nick: 'zerocho',
        password: 'nodejsbook',
      })
      .expect('Location', '/')
      .expect(302, done);
  });
});

describe('POST /join', () => {
  const agent = request.agent(app);
  beforeEach((done) => {
    agent
      .post('/auth/login')
      .send({
        email: 'zerohch0@gmail.com',
        password: 'nodejsbook',
      })
      .end(done);
  });

  test('์ด๋ฏธ ๋กœ๊ทธ์ธํ–ˆ์œผ๋ฉด redirect /', (done) => {
    const message = encodeURIComponent('๋กœ๊ทธ์ธํ•œ ์ƒํƒœ์ž…๋‹ˆ๋‹ค.');
    agent
      .post('/auth/join')
      .send({
        email: 'zerohch0@gmail.com',
        nick: 'zerocho',
        password: 'nodejsbook',
      })
      .expect('Location', `/?error=${message}`)
      .expect(302, done);
  });
});

describe('POST /login', () => {
  test('๊ฐ€์ž…๋˜์ง€ ์•Š์€ ํšŒ์›', (done) => {
    const message = encodeURIComponent('๊ฐ€์ž…๋˜์ง€ ์•Š์€ ํšŒ์›์ž…๋‹ˆ๋‹ค.');
    request(app)
      .post('/auth/login')
      .send({
        email: 'zerohch1@gmail.com',
        password: 'nodejsbook',
      })
      .expect('Location', `/?error=${message}`)
      .expect(302, done);
  });

  test('๋กœ๊ทธ์ธ ์ˆ˜ํ–‰', (done) => {
    request(app)
      .post('/auth/login')
      .send({
        email: 'zerohch0@gmail.com',
        password: 'nodejsbook',
      })
      .expect('Location', '/')
      .expect(302, done);
  });

  test('๋น„๋ฐ€๋ฒˆํ˜ธ ํ‹€๋ฆผ', (done) => {
    const message = encodeURIComponent('๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.');
    request(app)
      .post('/auth/login')
      .send({
        email: 'zerohch0@gmail.com',
        password: 'wrong',
      })
      .expect('Location', `/?error=${message}`)
      .expect(302, done);
  });
});

describe('GET /logout', () => {
  test('๋กœ๊ทธ์ธ ๋˜์–ด์žˆ์ง€ ์•Š์œผ๋ฉด 403', (done) => {
    request(app)
      .get('/auth/logout')
      .expect(403, done);
  });

  const agent = request.agent(app);
  beforeEach((done) => {
    agent
      .post('/auth/login')
      .send({
        email: 'zerohch0@gmail.com',
        password: 'nodejsbook',
      })
      .end(done);
  });

  test('๋กœ๊ทธ์•„์›ƒ ์ˆ˜ํ–‰', (done) => {
    agent
      .get('/auth/logout')
      .expect('Location', `/`)
      .expect(302, done);
  });
});

//ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ์‹œ ๋ฐ์ดํ„ฐ๋ฅผ ์ •๋ฆฌํ•˜๋Š” ์ฝ”๋“œ ์ถ”๊ฐ€
afterAll(async () => {  
  await sequelize.sync({ force: true });
});

 

 

5. ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ

  • ์„œ๋ฒ„๊ฐ€ ์–ผ๋งˆ๋งŒํผ์˜ ์š”์ฒญ์„ ๊ฒฌ๋”œ ์ˆ˜ ์žˆ๋Š”์ง€(์ˆ˜์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€) ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ฐฉ๋ฒ•
  • npm i -D artillery ํŒจํ‚ค์ง€ ์„ค์น˜
  • npm start ์„œ๋ฒ„ ์‹คํ–‰
  • npx artillery quick --count 100 -n 50 http://localhost:8001
  • loadtest.json ์ž‘์„ฑ
  • npx artillery run loadtest.json

 

 

[์ถœ์ฒ˜] ์กฐํ˜„์˜, ใ€ŽNode.js ๊ต๊ณผ์„œใ€, ๊ธธ๋ฒ—(2020), p521-560


๋นˆ์นธ ์ฑ„์šฐ๊ธฐ ๋ฌธ์ œ (๋นˆ์นธ์„ ๋“œ๋ž˜๊ทธํ•ด์„œ ์ •๋‹ต์„ ๋งžํ˜€ ๋ณด์„ธ์š”!)

 

1. ํ…Œ์ŠคํŠธ ๊ณผ์ •์—์„œ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ๋ถ„์„ํ•˜๋Š” ์˜ต์…˜์€ (jest --coverage)์ด๋‹ค.

2. ๋ชจํ‚น์„ ํ†ตํ•ด ์‹ค์ œ๋กœ ๋™์ž‘ํ•ด์•ผ ํ•˜๋Š” ๋ถ€๋ถ„ ๋Œ€์‹  (๊ฐ€์งœ ๊ฐ์ฒด, ๊ฐ€์งœ ํ•จ์ˆ˜, ๊ฐ€์งœ ๋ชจ๋“ˆ)๋กœ ๋Œ€์ฒดํ•  ์ˆ˜ ์žˆ๋‹ค.

3. ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํŒจํ‚ค์ง€๋Š” (jest), ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํŒจํ‚ค์ง€๋Š” (supertest), ๋ถ€ํ•˜ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ํŒจํ‚ค์ง€๋กœ๋Š” (artillery)๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

4. ์ด ์žฅ์—์„œ ์œ ๋‹› ํ…Œ์ŠคํŠธ๋Š” (ํ•จ์ˆ˜) ๋‹จ์œ„, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋Š” (๋ผ์šฐํ„ฐ) ๋‹จ์œ„์—์„œ ์ˆ˜ํ–‰ํ•ด๋ณด์•˜๋‹ค.

5. (OX ํ€ด์ฆˆ) ํ…Œ์ŠคํŠธ ํ™˜๊ฒฝ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๋”ฐ๋กœ ๊ตฌ์ถ•ํ•ด์•ผํ•œ๋‹ค. (์ •๋‹ต: O )


Node.js #2 

Editor : ์œ ์ฆˆ

 

728x90

๊ด€๋ จ๊ธ€ ๋”๋ณด๊ธฐ