본문 바로가기
개발자 도전기/[STUDY] TEST CODE

TDD | 테스트 주도 개발?

by 답수 2022. 5. 9.
728x90
SMALL

 

 

※ 인프런 - 테스트주도개발(TDD)로 만드는 NodeJS API 서버 강의를 기반으로 정리한 내용입니다.

 

 

TDD(Test Driven Development)

테스트 주도 개발(TDD)은 바로 소스코드를 작성하지 않고, 테스트 코드를 먼저 작성하는 방식을 말한다. 그러고 나서 테스트를 하나씩 통과해가면서 코드를 만들어간다. 강사님 같은 경우 API 서버를 만들 때 TDD 방식을 많이 사용한다고 한다. TDD를 사용하게 되면 개발하는데 시간이 많이 소요되지만, 프로젝트를 유지보수 하는 시점에 가면 TDD로 개발했던 것이 큰 효과를 발휘한다고 한다. 개발을 할 때, 개발을 하는 시간보다는 유지보수하는데 더 많은 시간이 들었고, 이때 TDD방식으로 개발했을 때 많은 도움이 되었다고 한다. (실제로 많은 기업에서 채용공고를 올릴 때, TDD방식을 요구하는 것을 많이 봤었다!)

 

강의를 듣기 전에 TDD를 어떤 방식으로 진행하는지 먼저 이해하고 싶어서 위키피디아를 찾아봤다. 개념은 강사님이 얘기하신 것처럼 소프트웨어를 완전히 개발하기 전에 테스트 케이스를 작성하고, 테스트의 요구사항에 의존하면서 소프트웨어를 개발하는 프로세스를 의미한다. 이를 통해 레거시 코드를 개선하고 디버깅할 수 있도록 한다.

 

테스트 주도 개발 사이클

 

1. 테스트 추가

2. 모든 테스트 실행. 새로운 테스트는 예상한 이유로 실패해야 한다.

3. 새로운 테스트를 통과하는 심플한 코드 작성

4. 모든 테스트는 통과되어야 한다.

5. 기능들이 정상적으로 유지되는지 확인하기 위해 리팩토링 실시, 이후 테스트를 통해 필요에 따라 리팩토링

6. 반복

 

또한 TDD는 작은 단위를 유지해야 한다. 이를 통해 얻을 수 있는 이점으로는 디버깅하는데 오류를 추적하기 더 수월하고, 코드를 읽고 이해하는데 더 쉽다는 것이다.

 

노드에서는 테스트 주도 개발을 위해서 mocha, should, SuperTest 이 세 가지의 라이브러리를 사용한다.

 

Mocha

모카(mocha)는 테스트 코드를 돌려주는, 실행시켜주는 테스트 러너를 말한다. (모카 문서 링크)

모카는 두 개의 파트로 나뉘어져 있다.

  1. 테스트 수트: 테스트 환경으로 모카에서는 describe()로 구현
  2. 테스트 케이스: 실제 테스트를 말하며 모카에서는 it()으로 구현

이제 간단한 테스트 코드를 테스트(?) 삼아 작성해보자.

// utils.js

function capitalize(str) {
    return str;
}
module.exports = {
    capitalize: capitalize,
};

 

위와 같은 함수가 있다고 해보자. 그리고 이를 테스트하기 위한 파일을 생성한다.

// utils.spec.js

/**
 * 일반적으로 자바스크립트 파일에서 .spec 이 붙으면 테스트 코드라고 보면 된다.
 * 명세서, 요구사항
 */

const utils = require('./utils');
const assert = require('assert');   // 노드의 기본 모듈

// 테스트 환경
describe('utils.js 모듈의 capitalize() 함수는 ', () => {
    it('문자열의 첫 번째 문자를 대문자로 변환한다', () => {
        // 실제 테스트 코드 작성. 검증할 수 있는 모듈 필요
        const result = utils.capitalize('hey');
        assert.equal(result, 'Hey');
    });
});

 

이 파일 같은 경우, .js 앞에 .spec이 붙어 있는데, 자바스크립트에서 보통 이게 붙으면 테스트코드라고 보면 된다(명세서, 요구사항이라고 이해).  

 

테스트 환경을 구현하는 테스트 수트 describe() 함수를 사용하고, 그 안에 실제로 테스트하는 테스트 케이스인 it() 함수를 사용한다.

 

그런데 노드 자체로는 테스트를 할 수 없기 때문에 mocha 모듈을 설치해야 한다.

npm i mocha --save-dev

 

모카는 node_modules/.bin에 설치되어 있으며, 테스트를 할 때는 다음과 같은 명령어러 실행할 수 있다.

node_modules/.bin/mocha '테스트할 파일이 있는 경로'
e.g. node_modules/.bin/mocha test/utils.spec.js

 

그럼 다음과 같은 결과가 나온다.

 

결과를 보자면 failing이 떴고, 기대한 값은 'Hey'인데 실제 값은 'hey'라고 그 이유가 나왔다.

 

그럼 utils.js의 함수를 다시 수정해서 통과하게 만들어보자.

// utils.js

function capitalize(str) {
    return str.substr(0, 1).toUpperCase() + str.substring(1);
}
module.exports = {
    capitalize: capitalize,
};

 

그럼 다음과 같은 결과가 나온다.

 

즉 이렇게 테스트 파일을 돌려주는 것이 모카의 역할이다.

 

Should

TDD 하는데 있어서 사용하는 두 번째 라이브러리는 should이다. node.js 공식 문서에 보면 테스트 코드에서는 assert를 사용하지 말고 서드파티 라이브러리를 사용하라고 명시되어 있다고 한다. 그래서 실제로 검증하는데 사용되는 라이브러리는 should 모듈이다. (should 문서 링크)

 

이제 assert에서 should로 바꿀 것이다. 역시 npm으로 should 설치!

npm i should --save-dev

 

// utils.spec.js

const utils = require('./utils');
const should = require('should');

// 테스트 환경
describe('utils.js 모듈의 capitalize() 함수는 ', () => {
    it('문자열의 첫 번째 문자를 대문자로 변환한다', () => {
        // 실제 테스트 코드 작성. 검증할 수 있는 모듈 필요
        const result = utils.capitalize('hey');
        result.should.be.equal('Hey');      // should 이용으로 테스트 코드의 가독성 향상
    });
});

 

결과는 역시 passing으로 잘 나온다. 이렇게 should를 이용하면 테스트 코드의 가독성을 높일 수 있다.

 

SuperTest

세 번째 라이브러리는 SuperTest이다. 지금까지 했던 테스트는 종류별로 보자면 단위 테스트(함수의 기능 테스트)였다. SuperTest는 통합 테스트(API의 기능 테스트)를 하는데 사용된다. 규모로 보자면 단위 데스트보다 더 크다. 슈퍼테스트는 익스프레스 통합 테스트용 라이브러리다. 내부적으로 익스프레스 서버를 구동시켜 실체 요청을 작성된 시나리오대로 보낸 뒤  결과를 검증한다. (SuperTest 코드 저장소 링크)

 

 슈퍼테스트의 깃허브에 들어가면 README에 예제가 있다.

const request = require('supertest');
const assert = require('assert');
const express = require('express');

const app = express();

app.get('/user', function(req, res) {
  res.status(200).json({ name: 'john' });
});

request(app)
  .get('/user')
  .expect('Content-Type', /json/)
  .expect('Content-Length', '15')
  .expect(200)
  .end(function(err, res) {
    if (err) throw err;
  });

request 변수에 supertest 모듈을 넣는다. 그리고 app에는 익스프레스 모듈을 넣은 객체를 만든다.

/user 라는 api를 만들고 이 app을 request() 안에 넣는다. 그 아래부터는 테스트  시나리오를 작성한다. get 메소드로 '/user'를 받고, 콘텐츠 타입은 json, 콘텐츠 길이는 15, 200번의 상태 코드를 받을 것이라고 코드를 작성하고, end를 통해 err면 err를 던지도록 작성.

 

그럼 직접 테스트를 한 번 해보자. supertest 모듈 먼저 설치 ㄱㄱ

npm i supertest --save-dev

 

먼저 index.js 파일이다.

const express = require('express');
const morgan = require('morgan');
const app = express();

const users = [
    {id: 1, name: '유재석'},
    {id: 2, name: '정준하'},
    {id: 3, name: '박명수'},
    {id: 4, name: '하하'},
];

app.use(morgan('dev'));

app.get('/users', function(req, res) {
    res.json(users);
});

app.listen(3000, function() {
    console.log('Example app listening on port 3000!');
})

module.exports = app;

 

이에 맞게 테스트코드도 작성한다. 이번에는 단순히 users api에서 users 배열이 제대로 전달되는지만 확인해본다.

const app = require('./index');
const request = require('supertest');

describe('GET /users는 ', () => {
    it('...', (done) => {
        request(app)
            .get('/users')
            .end((err, res) => {
                console.log(res.body);
                done();
            });
    });
});

 

결과는 

 

 

잘된다!

728x90
LIST

댓글