Skip to content

Latest commit

 

History

History
475 lines (327 loc) · 13.3 KB

File metadata and controls

475 lines (327 loc) · 13.3 KB

Testing

자동 코드 테스트

What is "Testing?"

수동 테스트

  • 장점 : 실제 유저처럼 앱을 사용해봅니다.
  • 단점 : 테스트 목적을 잊어버리거나 테스트를 잘 하지 못할 수 있습니다.

자동 테스트

  • 장점 : 중요한 모든 기능을 코드작성후에 자동으로 테스트 할 수 있습니다.
  • 단점 : 오직 정의한 부분만 테스트 할 수 있고 UI는 테스트가 불가능 합니다.\

unit test vs integration test

간단하기 unit test는 다른 모듈들과 상관없이 독립적으로 진행하는 테스트이고,
intergration test는 하나의 로직을 수행하면서 전체적인 흐름을 체크하는 테스트입니다.

Why "Testing"?

  1. 자동화 테스트는 코드가 변경 될 때마다 모든것을 테스트 합니다.
  2. 큰 변화를 쉽게 감지할 수 있습니다.(예상하지 못한곳도 테스트 할 수 있습니다.)
  3. 예측가능하고 명확하게 테스트 절차가 정해져있습니다.

Testing Tools & Setup

  1. Test 폴더에 mocha와 chai를 설치합니다.

    $ npm i --save-dev mocha chai

  2. package.json에 test script 수정하기

      "scripts": {
        "test": "mocha",
        "start": "nodemon app.js"
      },
  3. root 디렉토리에 test 폴더 생성하고 start.js 파일 만들기

  4. 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);
    });
  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)
  6. 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.

    실제 테스트 상황 가정하고 적용해보기

    1. auth unit 테스트하기

    로그인 인증과 관련된 테스트를 가정하고 진행해보도록 하겠습니다.

  • /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();
    };
  1. authHeader가 없다고 가정해보도록 하겠습니다.

  2. unit test를 진행할 것입니다.

    • unit test는 하나의 독립적인 모듈만 테스트 함으로 쉽게 오류를 확인할 수 있습니다.
  3. 가상의 유저가 보내는 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.');
      });
  4. 하지만 이 코드는 오류를 발생시킵니다.

    $ 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 객체를 던지기 때문입니다.

  5. 우리가 만든 프로젝트에서 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.'
      );
    });
  6. 올바른 결과

    $ 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)

2. 여러개의 테스트 진행하기

  • 테스트할 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라는 헤더를 가지게 되어 다른 테스트와 구별하기 쉽게 되어있습니다.

3. 토큰 인증 로직 테스트하기

외부 패키지는 정상적으로 작동하기 때문에 테스트할 필요가 없습니다. 우리의 프로젝트에서는 우리가 작성한 코드를 테스트하면 됩니다.

토큰은 임의로 만들어지기 때문에 테스트 코드에 올바른 JWT 토큰을 넣기가 어렵습니다. 따라서 아래의 로직을 따르도록 합니다.

  1. 임의의 req에 임의의 토큰을 부여합니다.

  2. 올바른 토큰이 없어서 발생하는 에러를 무시하고 진행합니다.

  3. is-auth.js 마지막에 req에 userId를 보내주는 방식을 이용합니다.

    req.userId = decodedToken.userId;

  4. 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 패키지는 원래의 함수를 보호하는 역할을 합니다.

      1. sinon 설치하기 npm install --save-dev sinon

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

4. 테스트 컨트롤러