[API 만들고 테스트하자]
- 그 전에 꼭 알아야할 것
1) 테스트 수트(describe) : 큰 카테고리 경우를 정의할 때 사용
2) 테스트 케이스(it) : 카테고리 내에서 테스트할 거리를 테스트할 때 사용
- 유저목록조회 API 테스트 코드
(1) 테스트 조건
- 유저목록조회 성공했을 때 유저객체를 담은 배열로 응답, 최대 limit 갯수 만큼 응답한다
- 유저목록조회 실패했을 때 limit(쿼리스트링)의 값이 숫자가 아니라면 400 코드를 응답한다
(2) 테스트 수트와 케이스로 나누기 : 테스트 바탕 조건과 테스트 해보아야 할 것을 구분
- 수트 : 전체 테스트, 성공, 실패
- 케이스 : 성공 - 유저객체를 담은 배열로 응답 - 최대 limit 갯수만큼 응답, 실패 - 400 코드 응답
(3) 케이스 만들기
/* app.js */
app.get('/users', function(req, res){
if(!req.query.limit){
res.json(users);
} else {
const limit = parseInt(req.query.limit, 10);
if(isNaN(limit)){
return res.status(400).end();
}
res.json(users.slice(0, limit));
}
});
/* app.spec.js */
const request = require('supertest');
const should = require('should');
/* have to generate to module */
const app = require('./app');
describe('GET /users는', () => {
/* 성공 케이스 테스트 */
describe('성공 시',() =>{
it('유저 객체를 담은 배열로 응답한다', (done) => {
request(app)
.get('/users')
.end((err, res)=>{
res.body.should.be.instanceOf(Array);
done();
});
});
it('최대 limit 갯수만큼 응답한다', (done) => {
request(app)
.get('/users?limit=2')
.end((err, res) =>{
res.body.should.have.lengthOf(2);
done();
});
});
});
/* 실패 케이스 테스트 */
describe('실패 시', () => {
it('limit이 숫자형이 아니면 400을 응답한다.', (done) =>{
request(app)
.get('/users?limit=ㅎㅇ')
.expect(400)
.end(done);
});
});
});
- 작성한 코드(app.js)가 mocha + should + supertest에 의해 테스트를 할 때 제대로 요청받고 응답하는지 테스트한 것
- 코드 이해가 되지않으면 전 포스팅이나 mocha, should, supertest api 문서를 참고
1) describe, it은 mocha
2) should be는 should, assertion types 검색
3) request.get.end는 supertest, end 내 콜백함수 (err,res)는 error와 result의 줄임말
- done은 app.get이 비동기 처리이기때문에 요청 - 응답 후(app.js) 테스트 끝내는 것
- 정리
1) '/users'에 요청을 했을 때 두가지 케이스에 대해 테스트하는 것
2) 두번째 케이스는 실패할 것 : 쿼리스트링 limit 값(임의 값)만큼 오지않았기 때문에
=> app.js 코드 변경 : req 객체를 통해 쿼리스트링(limit) 값 가져오고, 유저 배열에서 limit 값만큼 잘라서 응답하면 됨
app.get('/users', function(req, res){
const limit = req.query.limit;
res.json(users.slice(0, limit));
});
- 쿼리스트링이 있는지 따지고, 없다면 users 전체, 있다면 값이 숫자가 맞는지, 숫자가 맞다면 limit 갯수 만큼 리턴
1) parseInt와 isNaN 사용
2) res.end() : 응답 프로세스를 종료, node core가 동작한다고 express api 문서에 나와있음
3) status() : 기본값으로 200, 400을 설정하려면 인수로 설정
- expect로 응답 코드 결과 예상 설정, 별다른 것 없으면 .end(done)으로 테스트 끝내도 됨
- 유저 조회 API 테스트 코드
(1) 테스트 조건
- 조회 성공했을 때 id가 1인 유저 객체를 반환한다
- 조회 실패했을 때 id가 숫자가 아니면 400으로 응답한다, id로 유저를 찾을 수 없을 경우 404로 응답한다
(2) 테스트 수트와 케이스로 나누기
- 수트 : 전체 테스트, 성공, 실패
- 케이스 : 성공 - id 1인 유저 객체 반환, 실패 - id 숫자 아니면 400 응답 / id 유저를 찾을 수 없을 경우 404 응답
(3) 케이스 만들기
/* app.js */
app.get('/users/:id', function(req,res){
const id = parseInt(req.params.id, 10);
if(isNaN(id)){
return res.status(400).end();
}
const user = users.filter((user) => {
return user.id === id;
})[0];
if(!user){
return res.status(404).end();
}
res.json(user);
});
/* app.spec.js */
describe('GET /users/1은', () => {
/* 성공 케이스 */
describe('성공 시', () => {
it('id가 1인 유저 객체를 반환한다', (done) => {
request(app)
.get('/users/1')
.end((err, res) => {
res.body.should.have.property('id', 1);
done();
});
});
});
/* 실패 케이스 */
describe('실패 시', () => {
it('id가 아닐 경우 400으로 응답한다', (done) => {
request(app)
.get('/users/ㅁㄴㅇ')
.expect(400)
.end(done);
});
it('id로 유저를 찾을 수 없을 경우 404로 응답한다', (done) => {
request(app)
.get('/users/5000')
.expect(404)
.end(done);
});
});
});
- /users/:id 전달받은 id값은 req.params에 저장되어있음
- id값이 숫자가 아닐 때, 요청할 때 받은 id값으로 자원을 찾았는데 없을 때
- 유저 삭제 API 테스트 코드
(1) 테스트 조건
- 성공 시 : 204를 응답한다
- 실패 시 : id가 숫자가 아닐 경우 400으로 응답한다
(2) 테스트 수트와 케이스
- 수트 : 전체 테스트, 성공, 실패
- 케이스 : 204 응답 / 400 응답
(3) 케이스 만들기
/* app.js */
app.delete('/users/:id', function(req, res){
const id = parseInt(req.params.id, 10);
if(isNaN(id)){
return res.status(400).end();
}
users = users.filter((user) => {
return user.id !== id;
});
res.status(204).end();
});
/* app.spec.js */
describe('DELETE /users/1은', () =>{
/* 성공 케이스 */
describe('성공 시', () => {
it('204를 응답한다', (done)=>{
request(app)
.delete('/users/1')
.expect(204)
.end(done);
});
});
/* 실패 케이스 */
describe('실패 시', () => {
it('400을 응답한다', (done) => {
request(app)
.delete('/users/one')
.expect(400)
.end(done);
});
});
});
- 배열 요소 삭제 후 빈자리 채우는 로직을 만들기보다 filter로 특정 id값이랑 일치하지않는 요소만 걸러 기존 배열 덮어쓰기
- 204 코드 : 성공적인 응답을 했지만 콘텐츠 없을 때 사용
- 유저 추가 API 테스트 코드
(0) 필요한 라이브러리 : body-parser
- post 요청으로 날아오는 데이터를 파싱하기위해 사용할 라이브러리
- 기본적으로 express는 post 요청 데이터 파싱(body 파싱 - 태그 value)을 지원해주지않음 : 써드파티 모듈가지고 해결
- npm i body-parser --save
/* app.js */
const bodyParser = require('body-parser');
app.use(bodyParser.json()); // for parsing application/json
(1) 테스트 조건
- 성공 : 201코드 반환, 생성된 객체를 반환한다, 입력한 name을 반환한다
- 실패 : name 파라미터 누락 시 400 반환, name 중복의 경우 409 반환
(2) 테스트 수트 케이스 나누기
- 수트 : 전체, 성공, 실패
- 케이스 : 성공 3가지, 실패 2가지(400, 409 코드)
(3) 케이스 만들기
/* app.js */
app.post('/users', function(req, res){
const name = req.body.name;
if(!name){
return res.status(400).end();
}
const isExist = users.some((user) => {
return user.name === name;
});
if(isExist){
return res.status(409).end();
}
const id = Date.now();
const user = {
id: id,
name: name
};
users.push(user);
res.status(201).json(user);
});
/* app.spec.js */
describe('POST /users는', () => {
/* 성공 케이스 */
describe('성공 시', () => {
let body;
name = 'park-jinbro';
before((done) => {
request(app)
.post('/users')
.send({name: name})
.expect(201)
.end((err, res) => {
body = res.body;
done();
});
});
it('생성된 객체를 반환한다', () => {
body.should.have.be.property('id');
});
it('등록된 name을 반환한다', () => {
body.should.have.be.property('name', name);
});
});
/* 실패 케이스 */
describe('실패 시', () => {
it('name 값을 누락할 경우 400 반환', (done) => {
request(app)
.post('/users')
.send({})
.expect(400)
.end(done);
});
it('name 중복의 경우 409 반환', (done) =>{
request(app)
.post('/users')
.send({name: 'jinhyung'})
.expect(409)
.end(done);
});
});
});
- 3가지 결과를 테스트 할 때 사용하는 코드가 똑같음 : mocha.js의 before를 사용하기
- before은 비동기처리를 해야하기때문에 done, it은 동기처리로 테스트하기때문에 done이 필요없음
- before이나 it이 실행되기전(실제 테스트 실행되기전) 필요한 변수 선언 후 사용(공통적으로 사용된다던지....)
- 데이터 중복 기능 체크 시 중복되는 데이터를 넣어봐야 테스트가 가능함 : 그렇지않으면 201 반환해서 테스트케이스 오류
- 유저 수정 API 테스트 코드
(1) 테스트 조건
- 성공 : 수정할 객체 반환
- 실패 : id가 숫자가 아닐 경우 400 코드 반환, id에 해당하는 유저가 없을 경우 404 코드 반환
(2) 테스트 수트, 케이스 나누기
- 수트 : 전체, 성공, 실패
- 케이스 : 성공 시 - 수정 객체 반환 / 실패 시 - 400코드, 404 코드 반환
(3) 케이스 만들기
/* app.js */
app.put('/users/:id', function(req, res){
const id = parseInt(req.params.id, 10);
const name = req.body.name;
if(isNaN(id) || !name){
return res.status(400).end();
}
const isConflict = users.some((user) => {
return user.name === name;
});
if(isConflict){
return res.status(409).end();
}
const user = users.filter((user) => {
return user.id === id;
})[0];
if(!user){
return res.status(404).end();
}
user.name = name;
res.json(user);
});
/* app.spec.js */
describe('PUT /users/2는', () => {
/* 성공 케이스 */
describe('성공 시', () => {
it('변경된 name을 반환함', (done) => {
let name = 'jinbro-park';
request(app)
.put('/users/2')
.send({name: name})
.end((err, res) => {
res.body.should.have.property('name', name);
done();
});
});
});
/* 실패 케이스 */
describe('실패 시', () => {
it('id가 숫자가 아닐 경우 400 코드 응답', (done) => {
request(app)
.put('/users/two')
.expect(400)
.end(done);
});
it('name 값이 누락되었을 경우 400 코드 응답', (done) => {
request(app)
.put('/users/2')
.send({})
.expect(400)
.end(done);
});
it('id 값으로 유저를 찾지 못했을 경우 404 코드 응답', (done) => {
request(app)
.put('/users/999')
.send({name: 'fake'})
.expect(404)
.end(done);
});
it('name 값이 이미 존재하는 경우 409 코드 응답', (done) => {
request(app)
.put('/users/2')
.send({name: 'park'})
.expect(409)
.end(done);
});
});
});
[정리]
- 테스트 수트와 케이스를 나누다보니 에러 경우의 수를 자연스럽게 생각하게됨 : 성공부터 짜고 실패 부분 짜기
- 테스트할 경우의 수를 생각하다보니 자연스럽게 어떤 컨트롤을 넣어야할지 알겠음 : 하나씩 테스트 하면서 만들기, 여러개 한꺼번에 하면......
- 테스트 코드 짜는게 어려운 것이 아니니깐 하나씩 테스트하면서 코딩해도 되겠음
- 자료구조는 언제나 중요함 : 설계할 때도 설계하고 사용할 때도 자료구조 공부를 한 것이...
- 아래는 테스트 결과 이미지
[참고자료]
- inflearn, 테스트주도개발(TDD)로 만드는 NodeJS API 서버 : https://goo.gl/3fEGPp
- 자바스크립트 isNaN, parseInt, control
- express, API 참조 : http://expressjs.com/ko/4x/api.html
- github, supertest.js : https://github.com/visionmedia/supertest
- github page, should.js : https://shouldjs.github.io/
'nodejs' 카테고리의 다른 글
[node.js] express - HTTP 쿠키 사용하기 (0) | 2017.06.18 |
---|---|
[node.js] AJAX API 서버 만들기 (0) | 2017.06.17 |
[node.js] TDD 라이브러리 - mocha, assert, should, supertest (0) | 2017.06.12 |
[node.js] express로 REST API 서버 개발 (0) | 2017.06.09 |
[node.js] npm, package.json 제대로 알고 가기 (0) | 2017.06.08 |
댓글