자동 코드 테스트
- 장점 : 실제 유저처럼 앱을 사용해봅니다.
- 단점 : 테스트 목적을 잊어버리거나 테스트를 잘 하지 못할 수 있습니다.
- 장점 : 중요한 모든 기능을 코드작성후에 자동으로 테스트 할 수 있습니다.
- 단점 : 오직 정의한 부분만 테스트 할 수 있고 UI는 테스트가 불가능 합니다.\
간단하기 unit test는 다른 모듈들과 상관없이 독립적으로 진행하는 테스트이고,
intergration test는 하나의 로직을 수행하면서 전체적인 흐름을 체크하는 테스트입니다.
- 자동화 테스트는 코드가 변경 될 때마다 모든것을 테스트 합니다.
- 큰 변화를 쉽게 감지할 수 있습니다.(예상하지 못한곳도 테스트 할 수 있습니다.)
- 예측가능하고 명확하게 테스트 절차가 정해져있습니다.
-
Test 폴더에 mocha와 chai를 설치합니다.
$ npm i --save-dev mocha chai -
package.json에 test script 수정하기
"scripts": { "test": "mocha", "start": "nodemon app.js" },
-
root 디렉토리에 test 폴더 생성하고 start.js 파일 만들기
-
start.js
const { expect } = require('chai'); //인자1: 이름 인자2: 실항할 로직 함수 it('should add numbers correctly', function () { const num1 = 2; const num2 = 3; // chai를 사용해서 기대하는 값 설정하기 expect(num1 + num2).to.equal(5); });
-
npm test하고 결과 확인하기
> nodejs-complete-guide@1.0.0 test C:\Users\kooks\Desktop\StudyInGit\AllaboutNode\Project_test ct_test > mocha √ should add numbers correctly 1 passing (11ms)
-
test 실패 했을 때
-
test 코드
const { expect } = require('chai'); //인자1: 이름 인자2: 실항할 로직 함수 it('should add numbers correctly', function () { const num1 = 2; const num2 = 3; // chai를 사용해서 기대하는 값 설정하기 expect(num1 + num2).not.to.equal(5); });
-
bash
> nodejs-complete-guide@1.0.0 test C:\Users\kooks\Desktop\StudyInGit\AllaboutNode\Project_test > mocha 1) should add numbers correctly 0 passing (16ms) 1 failing 1) should add numbers correctly: AssertionError: expected 5 to not equal 5 + expected - actual at Context.<anonymous> (test\start.js:8:30) npm ERR! Test failed. See above for more details.
로그인 인증과 관련된 테스트를 가정하고 진행해보도록 하겠습니다.
-
-
/middleware/is-auth.js 코드
const jwt = require('jsonwebtoken'); module.exports = (req, res, next) => { const authHeader = req.get('Authorization'); if (!authHeader) { const error = new Error('Not authenticated.'); error.statusCode = 401; throw error; } const token = authHeader.split(' ')[1]; let decodedToken; try { decodedToken = jwt.verify(token, 'somesupersecretsecret'); } catch (err) { err.statusCode = 500; throw err; } if (!decodedToken) { const error = new Error('Not authenticated.'); error.statusCode = 401; throw error; } req.userId = decodedToken.userId; next(); };
-
authHeader가 없다고 가정해보도록 하겠습니다.
-
unit test를 진행할 것입니다.
- unit test는 하나의 독립적인 모듈만 테스트 함으로 쉽게 오류를 확인할 수 있습니다.
-
가상의 유저가 보내는 req를 보내도록 하겠습니다.
-
/test/auth-middleware.js
const { expect } = require('chai'); const authMiddleware = require('../middleware/is-auth'); it('should throw an error if no authorization header is present', function () { // 가상의 request const req = { get: function (headerName) { return null; } }; expect(authMiddleware(req, {}, () => {})).to.throw('Not authenticated.'); });
-
-
하지만 이 코드는 오류를 발생시킵니다.
$ npm test > nodejs-complete-guide@1.0.0 test C:\Users\kooks\Desktop\StudyInGit\AllaboutNode\Project_test > mocha 1) should throw an error if no authorization header is present 0 passing (35ms) 1 failing 1) should throw an error if no authorization header is present: Error: Not authenticated. at module.exports (middleware\is-auth.js:6:19) at Context.<anonymous> (test\auth-middleware.js:11:10) npm ERR! Test failed. See above for more details.
그 이유는 middleware의 is-auth.js에서 Error 객체를 던지기 때문입니다.
-
우리가 만든 프로젝트에서 Error를 던지는 것이 아닌 테스트 프레임 워크에서 Error를 던지기 위해서 bind를 사용하도록 하겠습니다.
it('should throw an error if no authorization header is present', function () { const req = { get: function (headerName) { return null; } }; // bind 하기 expect(authMiddleware.bind(this, req, {}, () => {})).to.throw( 'Not authenticated.' ); });
-
올바른 결과
$ npm test > nodejs-complete-guide@1.0.0 test C:\Users\kooks\Desktop\StudyInGit\AllaboutNode\Project_test > mocha √ should throw an error if no authorization header is present 1 passing (17ms)
-
테스트할 is`auth.js 코드
const jwt = require('jsonwebtoken'); module.exports = (req, res, next) => { const authHeader = req.get('Authorization'); if (!authHeader) { const error = new Error('Not authenticated.'); error.statusCode = 401; throw error; } const token = authHeader.split(' ')[1]; let decodedToken; try { decodedToken = jwt.verify(token, 'somesupersecretsecret'); } catch (err) { err.statusCode = 500; throw err; } if (!decodedToken) { const error = new Error('Not authenticated.'); error.statusCode = 401; throw error; } req.userId = decodedToken.userId; next(); };
위 코드는 헤더에 토큰을 보내주는 로직입니다. 이때 토큰을 헤더에 보내주는 방식을 테스트 해봅시다.
-
테스트를 진행하면 아래와 같은 결과를 얻을 수 있습니다.
-
테스트 코드
const { expect } = require('chai'); const authMiddleware = require('../middleware/is-auth'); it('should throw an error if no authorization header is present', function () { // 가상의 request const req = { get: function (headerName) { return null; } }; expect(authMiddleware.bind(this, req, {}, () => {})).to.throw( 'Not authenticated.' ); }); it('should throw an error if the authorization header is only one string', function () { const req = { get: function (headerName) { return 'xyz'; } }; expect(authMiddleware.bind(this, req, {}, () => {})).to.throw; });
-
결과
$ npm test > nodejs-complete-guide@1.0.0 test C:\Users\kooks\Desktop\StudyInGit\AllaboutNode\Project_test > mocha √ should throw an error if no authorization header is present √ should throw an error if the authorization header is only one string 2 passing (22ms)
아직 두개의 테스트만 존재함으로 구별하기가 쉽습니다. 하지만 테스트가 늘어나면 어디서 오류가 생겼는지 찾기 어려울 수 있습니다. 이럴때 describe 함수를 사용하면 됩니다.
-
describe 함수 사용하기
-
/test/auth-middleware.js
const { expect } = require('chai'); const authMiddleware = require('../middleware/is-auth'); // 기존 테스트 함수들을 describe안에 넣기 describe('Auth Middleware', function () { it('should throw an error if no authorization header is present', function () { // 가상의 request const req = { get: function (headerName) { return null; } }; expect(authMiddleware.bind(this, req, {}, () => {})).to.throw( 'Not authenticated.' ); }); it('should throw an error if the authorization header is only one string', function () { const req = { get: function (headerName) { return 'xyz'; } }; expect(authMiddleware.bind(this, req, {}, () => {})).to.throw; }); });
-
-
결과
$ npm test > nodejs-complete-guide@1.0.0 test C:\Users\kooks\Desktop\StudyInGit\AllaboutNode\Project_test > mocha Auth Middleware √ should throw an error if no authorization header is present √ should throw an error if the authorization header is only one string 2 passing (52ms)
결과를 보면 Auth Middleware라는 헤더를 가지게 되어 다른 테스트와 구별하기 쉽게 되어있습니다.
-
외부 패키지는 정상적으로 작동하기 때문에 테스트할 필요가 없습니다. 우리의 프로젝트에서는 우리가 작성한 코드를 테스트하면 됩니다.
토큰은 임의로 만들어지기 때문에 테스트 코드에 올바른 JWT 토큰을 넣기가 어렵습니다. 따라서 아래의 로직을 따르도록 합니다.
-
임의의 req에 임의의 토큰을 부여합니다.
-
올바른 토큰이 없어서 발생하는 에러를 무시하고 진행합니다.
-
is-auth.js 마지막에 req에 userId를 보내주는 방식을 이용합니다.
req.userId = decodedToken.userId; -
auth-middleware.js
-
4.1
it('should yield a userId after decoding the token', function () { const req = { get: function (headerName) { return 'Bearer alswocjswo'; } }; // is-auth.js에 있는 원래 jwt.verify 덮어씌우기 jwt.verify = function () { return { userId: 'abc' }; }; authMiddleware(req, {}, () => {}); console.log(jwt.verify); expect(req).to.have.property('userId'); });
위 코드처럼 마지막에 req.userId가 존재하는지 체크해서 토큰 인증이 올바르게 됐는지 확인합니다.
Auth Middleware √ should throw an error if no authorization header is present √ should throw an error if the authorization header is only one string √ should throw an error if the token cannot be verified √ should yield a userId after decoding the token
하지만 이 방법은 우하하지 않습니다. jwt.verify를 덮어씌우므로 성능 저하가 발생할 수 있고 올바른 코드 작성 방법이 아닙니다. 그리고 세번째와 네번째 테스트 함수의 순서를 바꾸게 되면 네번째로간
it('should throw an error if the token cannot be verified', function () { const req = { get: function (headerName) { return 'Bearer xyz'; } }; expect(authMiddleware.bind(this, req, {}, () => {})).to.throw(); });
이 함수에서 오류가 발생합니다. 왜냐하면 토큰 인증 함수가 적절하게 이루어졌기 때문입니다.
-
4.2 올바른 방법
sinon 패키지를 사용합니다. sinon 패키지는 원래의 함수를 보호하는 역할을 합니다.
-
sinon 설치하기
npm install --save-dev sinon -
test 함수 다시 작성하기
it('should yield a userId after decoding the token', function () { const req = { get: function (headerName) { return 'Bearer alswocjswo'; } }; // 1. 우리가 사용하고자 하는 함수 sinon에 인자로 넣어주기 // 인자1: 유지하고자 하는 함수 인자2: 함수 설명 sinon.stub(jwt, 'verify'); // 2. 리턴하고 싶은 값을 설정하기 jwt.verify.returns({ userId: 'abc' }); authMiddleware(req, {}, () => {}); expect(req).to.have.property('userId'); expect(req).to.have.property('userId', 'abc'); // 3. 원래 함수로 돌리기 jwt.verify.restore(); });
-
-