다음 포스팅에서 사용한 sns 테스트에 대한 자세한 설명 글 입니다.
해당 github에는 주석으로 설명을 하지 않았지만 이번 포스팅에서 따로 설명을 추가하겠습니다.
https://github.com/Ysungho/node
GitHub - Ysungho/node: 노드를 사용해서 만든 블로그 입니다. Trip_share의 데모 버전 입니다.
노드를 사용해서 만든 블로그 입니다. Trip_share의 데모 버전 입니다. . Contribute to Ysungho/node development by creating an account on GitHub.
github.com
다음 github 주소에서 nodebird-api와 nodecat을 참조하면 됩니다
https://brilliant-star.tistory.com/173
[Node.js]sns, 채팅 서비스 제작: Trip_Share 데모 버전
해당 프로젝트는 Trip_Share를 제작하기 이전에 미리 제작한 데모 버전 입니다. 사용한 코드는 제일 하단에 링크를 첨부했습니다. 이 밖에 사용한 기술에 대한 설명은 다음 링크를 참고해주시기 바
brilliant-star.tistory.com
npm i -D jest
npm test
//테스트 커버리지
npm run coverage
//통합 테스트
npm i -D supertest
//부하테스트
npm i -D artillery
npm start
npx artillery quick --count 100 -n 50 <http://localhost:8001>
npx artillery run loadtest.json
테스트의 역할은 코드나 함수가 제대로 실행되는지를 검사하고 값이 일치하는지를 검사하는 것이므로, 테스트 코드의 객체가 실제 익스프레스 객체가 아니어도 됩니다.
이렇게 가짜 객체, 가짜 함수를 넣는 행위를 ***모킹(mocking)***이라고 합니다.
routes/middleware.test.js
const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
describe('isLoggedIn', () => {
const res = {
status: jest.fn(() => res),
send: jest.fn(),
};
const next = jest.fn();
test('로그인 되어있으면 isLoggedIn이 next를 호출해야 함', () => {
const req = {
isAuthenticated: jest.fn(() => true),
};
isLoggedIn(req, res, next);
expect(next).toHaveBeenCalledTimes(1);
});
test('로그인 되어있지 않으면 isLoggedIn이 에러를 응답해야 함', () => {
const req = {
isAuthenticated: jest.fn(() => false),
};
isLoggedIn(req, res, next);
expect(res.status).toBeCalledWith(403);
expect(res.send).toBeCalledWith('로그인 필요');
});
});
describe('isNotLoggedIn', () => {
const res = {
redirect: jest.fn(),
};
const next = jest.fn();
test('로그인 되어있으면 isNotLoggedIn이 에러를 응답해야 함', () => {
const req = {
isAuthenticated: jest.fn(() => true),
};
isNotLoggedIn(req, res, next);
const message = encodeURIComponent('로그인한 상태입니다.');
expect(res.redirect).toBeCalledWith(`/?error=${message}`);
});
test('로그인 되어있지 않으면 isNotLoggedIn이 next를 호출해야 함', () => {
const req = {
isAuthenticated: jest.fn(() => false),
};
isNotLoggedIn(req, res, next);
expect(next).toHaveBeenCalledTimes(1);
});
});
작은 단위의 함수나 모듈이 의도된 대로 정확히 작동하는지 테스트 하는 것을 유닛테스트(unit test) 또는 단위 테스트라고 부릅니다.
나중에 함수를 수정하면 기존에 작성해둔 테스트는 실패하게 됩니다. 따라서 함수가 수정되었을 때 어떤 부분이 고장나는지를 테스트를 통해 알 수 있습니다.
테스트 코드도 기존 코드가 변경된 것에 맞춰서 수정해야 합니다.
통합 테스트
하나의 라우터에는 여러 개의 미들웨어가 붙어 있고, 다양한 라이브러리가 사용됩니다. 이런것들이 모두 유기적으로 잘 작동하는지 테스트하는 것이 통합테스트 입니다.
routes.auth.test.js
const request = require('supertest');
const { sequelize } = require('../models');
const app = require('../app');
beforeAll(async () => {
await sequelize.sync();
});
//==1==
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 /login', () => {
const agent = request.agent(app);//==2==
//==3==
beforeEach((done) => {
agent
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.end(done);
});
//==4==
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('가입되지 않은 회원', async (done) => {
const message = encodeURIComponent('가입되지 않은 회원입니다.');
request(app)
.post('/auth/login')
.send({
email: 'zerohch1@gmail.com',
password: 'nodejsbook',
})
.expect('Location', `/?loginError=${message}`)
.expect(302, done);
});
test('로그인 수행', async (done) => {
request(app)
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'nodejsbook',
})
.expect('Location', '/')
.expect(302, done);
});
test('비밀번호 틀림', async (done) => {
const message = encodeURIComponent('비밀번호가 일치하지 않습니다.');
request(app)
.post('/auth/login')
.send({
email: 'zerohch0@gmail.com',
password: 'wrong',
})
.expect('Location', `/?loginError=${message}`)
.expect(302, done);
});
});
describe('GET /logout', () => {
test('로그인 되어있지 않으면 403', async (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('로그아웃 수행', async (done) => {
const message = encodeURIComponent('비밀번호가 일치하지 않습니다.');
agent
.get('/auth/logout')
.expect('Location', `/`)
.expect(302, done);
});
});
afterAll(async () => {
await sequelize.sync({ force: true });
});
- 첫번재 describe 에서는 회원 가입 테스트를 합니다
- 두번 째 describe에서는 로그인한 상태에서 회원가입을 시도하는 경우를 테스트합니다. 이때 코드의 순서가 매우 중요합니다. 로그인한 상태여야 회원가입을 테스트 할 수 있으므로 로그인 요청과 회원가입 요청이 순서대로 이루어져야 합니다. 이때 agent를 만들어서 하나 이상의 요청에서 재사용할 수 있습니다.
- beforeEach는 각 테스트 실행에 앞서 먼저 실행되는 부분입니다. 회원 가입 테스트를 위해 아까 생성한 agent 객체로 로그인을 먼저 수행합니다. end(done)으로 beforeEach 함수가 마무리되었음을 알려야 합니다.
- 로그인된 agent로 회원가입 테스트를 진행합니다. 로그인한 상태이므로 ‘로그인한 상태입니다’ 라는 에러 메시지와 함께 리다리렉트됩니다.
부하 테스트
서버가 얼마만큼의 요청을 견딜 수 있는지 테스트하는 방법입니다.
코드에 문법적, 논리적 문제가 없더라도 서버의 하드웨어 제약 때문에 서비스가 중단될 수 있습니다.
대표적인 것이 OOM(OUT OF MEMORY) 문제입니다. 서버는 접속자들의 정보를 저장하기 위해 각각의 접속자마다 일정한 메모리를 할당합니다.
이렇게 사용하는 메모리의 양이 증가하다가 서버의 메모리 용량을 넘어서게 되면 문제가 발생합니다.
loadtest.json
{
"config":{
"target": "<http://localhost:8001>",
"phases": [
{
"duration": 60,
"arrivalRate": 30
}
]
},
"scenarios": [{
"flow": [{
"get": {
"url": "/"
}
}, {
"post": {
"url": "/auth/login",
"json": {
"email": "zerohch0@naver.com",
"password": "nodejsbook"
}
}
}, {
"get": {
"url": "/hashtag?hashtag=nodebird"
}
}]
}]
}
부하 테스트를 할 때 단순히 한 페이지에만 요청을 보내는 것이 아니라 실제 사용자의 행동을 모방하여 시나리오를 작성할 수 있습니다. 이때는 json 형식의 설정 파일을 작성해야 합니다.
중간 로그부터 봤다면 테스트를 진행할 수록 요청을 처리하는 속도가 점점 느려짐을 알 수 있습니다.
이는 서버가 지금 부하 테스트를 하는 정도의 요청을 감당하지 못한다는 뜻 입니다. 따라서 이 문제를 해결할 방법을 고민해 봐야 합니다.
서버의 사양을 업그레이드하거나, 서버를 여러개 두거나, 코드를 더 효율적으로 개선하는 방법이 있습니다.
일반적으로 요청-응답 시 데이터베이스에 접근할 때 가장 많은 시간이 소요됩니다.
서버는 여러 대로 늘리기 쉽지만, 데이터베이스는 늘리기 어려우므로 하나의 데이터베이스에 많은 요청이 물리곤 합니다.
따라서 최대한 데이터베이스에 접근하는 요청을 줄이면 좋습니다. 반복적으로 가져오는 데이터는 캐싱을 한다든지 하여 데이터베이스에 접근하는 일을 줄이도록 합시다.
서버의 성능과 네트워크 상황에 따라 다르지만 arrivalRate를 줄이거나 늘려서 자신의 서버가 어느 정도의 요청을 수용할 수 있을지 체크해보는 것이 좋습니다.
또한 한 번만 테스트하는게 아니라 여러 번 같은 설정 값으로 테스트하여 평균치를 내보는게 좋습니다.
다른 테스트 방법
대표적으로 시스템 테스트와 인수 테스트가 있습니다.
회사에서 QA들이 테스트 목록ㅇ르 두고 체크해나가며 진행하는 테스트가 주로 시스템 테스트이고,
알파테스트나 베타 테스트 처럼 특정 사용자 집단이 실제 서비스를 사용하는 것 처럼 진행하는 테스트가 인수 테스트입니다.
가능한 한 다양한 종류의 테스트를 주기적으로 수행해 서비스를 안정적으로 유지하고 점검하는 것이 좋습니다.
핵심 정리
- 테스트를 작성한다고 해서 에러가 발생하지 않는 것은 아닙니다. 하지만 자신의 코드에 대한 믿음을 가질 수 있습니다.
- 테스트를 올바르게 작성하지 않으면 테스트를 하지 않는 것보다 못한 상황이 발생합니다.
- 테스트를 작성하면 나중에 코드 변경 사항이 생겼을 때 어떤 부분에 영향을 미치는지 쉽게 파악할 수 있습니다. 이러한 긍정적인 영향과 테스트하는데 필요한 공수를 함께 고려해서 테스트할 범위를 정해야 합니다.
- 실제 서비스에서는 모든 기능을 테스트하기가 어려우므로 우선 순위를 정하여 우선순위가 높은 기능 위주로 테스트합니다
- 테스트 커러리지가 100%라고 해서 에러가 발생하지 않는 것은 아닙니다.
'개발후기 > node.js' 카테고리의 다른 글
[Node.js]채팅 서비스 제작: Trip_Share 데모 버전 (0) | 2023.09.07 |
---|---|
[Node.js]node 게시판 api (0) | 2023.09.06 |
[Node.js]sns 서비스 제작: Trip_Share 데모 버전 (0) | 2023.09.06 |
[Node.js]Trip Share (0) | 2023.09.03 |
[node.js]지역 사회 마켓-경매 시스템 (0) | 2023.05.30 |