따라하기 실습에서 사용하는 node.js 웹 프레임워크는 express 입니다. express는 왠만한 웹 애플리케이션 프레임워크 수준입니다. 보면 볼수록 괜찮다는 느낌이 드는 프레임워크인데요, 놀랍게도 firejune이라는 분께서 매뉴얼을 한글로 번역해 놓으셨습니다. Node.JS용 MVC 프레임워크 Express - 문서 번역. node.js관련해서 이런저런 사이트를 언급했었는데요, 이런 사이트도 있었는 줄 몰랐습니다. 이 외에도 이분의 블로그에는 node.js 관련된 좋은 내용이 참 많이 있습니다. 참고하시면 많은 도움 되실겁니다. 기회봐 여쭤봐서 오프라인 세미나때 초청해 볼 생각입니다. (잘 되야 할텐데 말입니다.ㅎㅎ)
여튼 그에 필 받아서 저는 Express에서도 쓸수 있고, 우리의 따라배우기 강좌에도 나오는 node.js 테스트 프레임워크인 Expresso 매뉴얼을 번역해 봅니다. 지금은 그냥 한번 쭉~ 훓어 보시고, 따라배우기 스터디 도중 중간중간 필요하실때 참고 하시면 될 것 같습니다. :D
참! 훅~딱~ 번역해서 오역/오타 있을수 있습니다. 눈에 띄시면 코멘트로 남겨주세요~
Expresso
원문: http://visionmedia.github.com/expresso/
Expresso는 nodejs에서 사용하는 자바스크립트TDD 프레임워크입니다. . Expresso는 매우 빠르며, 기본 단정문 이외의 추가적인 단정문(assertion) 메소드와 코드 커버리지 레포트, CI 지원 등을 갖추고 있습니다. .
따라하기 실습에서 사용하는 node.js 웹 프레임워크는 express 입니다. express는 왠만한 웹 애플리케이션 프레임워크 수준입니다. 보면 볼수록 괜찮다는 느낌이 드는 프레임워크인데요, 놀랍게도 firejune이라는 분께서 매뉴얼을 한글로 번역해 놓으셨습니다. Node.JS용 MVC 프레임워크 Express - 문서 번역. node.js관련해서 이런저런 사이트를 언급했었는데요, 이런 사이트도 있었는 줄 몰랐습니다. 이 외에도 이분의 블로그에는 node.js 관련된 좋은 내용이 참 많이 있습니다. 참고하시면 많은 도움 되실겁니다. 기회봐 여쭤봐서 오프라인 세미나때 초청해 볼 생각입니다. (잘 되야 할텐데 말입니다.ㅎㅎ)
여튼 그에 필 받아서 저는 Express에서도 쓸수 있고, 우리의 따라배우기 강좌에도 나오는 node.js 테스트 프레임워크인 Expresso 매뉴얼을 번역해 봅니다. 지금은 그냥 한번 쭉~ 훓어 보시고, 따라배우기 스터디 도중 중간중간 필요하실때 참고 하시면 될 것 같습니다. :D
참! 훅~딱~ 번역해서 오역/오타 있을수 있습니다. 눈에 띄시면 코멘트로 남겨주세요~
Features
- 경량
- 직관적인 비동기 지원
- 직관적인 테스트 러너 실행
- node-jscoverage를 통한 테스트 커버리지 지원
- 코어
assert
module 사용과 확장지원 assert.deepEqual()의 별칭으로
assert.eql()
사용assert.response()
http response 유틸리티assert.includes()
assert.isNull()
assert.isUndefined()
assert.isNotNull()
assert.isDefined()
assert.match()
assert.length()
설치
expresso와 node-jscoverage를 설치하려면 다음과 같은 명령어를 사용한다. node-jscoverage가 먼저 컴파일 된다.
$ make install
커버리지 레포트 없이 expresso 단독으로 설치하려면 다음과 같이
$ make install-expresso
npm을 이용해서 설치하려면 아래와 같이
$ npm install expresso
예제들
테스트 케이스를 정의하기 위해 함수들을 export 합니다.
exports['test String#length'] = function(){
assert.equal(6, 'foobar'.length);
};
혹은, 다수의 테스트를 실행하기 위해서 테스트들을 포함하고 있는 객체를 export 하고 싶을 수도 있습니다.
module.exports = {
'test String#length': function(beforeExit, assert) {
assert.equal(6, 'foobar'.length);
}
};
괄호로 된 키 값을 설정하고 싶지 않다면 다음과 같이 합니다.
exports.testsStringLength = function(beforeExit, assert) {
assert.equal(6, 'foobar'.length);
};
콜백 함수로 전달 된 인자는 beforeExit
와 assert입니다.각각의 테스트 함수에서 ("this")는 테스트할 객체를 지칭합니다.
두번째 파리미터인 beforeExit
인자로 함수를 전달해서 테스트를 끝내기 전에 넘긴 해당 함수를 실행할 수 있습니다. 실제로 테스트가 잘 수행되었는지 확인할때 요긴합니다. beforeExit
는 this에 대한 exit 이벤트를 리스닝하는 단축 명령어에 해당합니다.assert
는 테스트 대상 객체에 대한 assert
입니다. 비동기 콜백에서 단정문들이 올바른 테스트와 연결되는 것을 확실하게 만들어 줍니다.
exports.testAsync = function(beforeExit, assert) {
var n = 0;
setTimeout(function() {
++n;
assert.ok(true);
}, 200);
setTimeout(function() {
++n;
assert.ok(true);
}, 200);
// 테스트가 끝나면, exit 이벤트가 호출된다.
this.on('exit', function() {
assert.equal(2, n, 'Ensure both timeouts are called');
});
// 대안으로, beforeExit를 단축명령어로 사용할 수 있다.
beforeExit(function() {
assert.equal(2, n, 'Ensure both timeouts are called');
});
};
단정문 유틸리티들 Assert Utilities
assert.isNull(val[, msg])
val은
null이어야 한다!
assert.isNull(null);
assert.isNotNull(val[, msg])
val
은 null이 아니다!
assert.isNotNull(undefined);
assert.isNotNull(false);
assert.isUndefined(val[, msg])
val은
undefined 상태이어야 한다.
assert.isUndefined(undefined);
assert.isDefined(val[, msg])
val은
undefined상태가 아니어야 한다.
assert.isDefined(null);
assert.isDefined(false);
assert.match(str, regexp[, msg])
str이 정규표현식(
regexp)과 일치해야 한다.
assert.match('foobar', /^foo(bar)?/);
assert.match('foo', /^foo(bar)?/);
assert.length(val, n[, msg])
val의 길이(
length)는
n이어야 한다.
assert.length([1,2,3], 3);
assert.length('foo', 3);
assert.type(obj, type[, msg])
obj는
타입이 같아야 한다.
assert.type(3, 'number');
assert.eql(a, b[, msg])
객체 b는
객체 a와 같아야
한다. 코어 assert.deepEqual()메소드
의 별칭에 해당한다. ==를 사용하는 assert.equal()와는 정 반대다.
assert.eql('foo', 'foo');
assert.eql([1,2], [1,2]);
assert.eql({ foo: 'bar' }, { foo: 'bar' });
assert.includes(obj, val[, msg])
obj가
val을 포함해야 한다. 배열(
Array
s)과 문자열(Strings)을 지원한다.
assert.includes([1,2,3], 3);
assert.includes('foobar', 'foo');
assert.includes('foobar', 'bar');
assert.response(server, req, res|fn[, msg|fn])
listen()을 호출하지 않고 주어진 server에서 단정문을 수행한다. expresso에 의해 내부적으로 수행되고 모든 응답이 완료되면 서버가 중단된다. (listen을 하면 외부 입력을 받게 되므로 해당 메소드를 호출하지 않고 내부적으로 진행된다는 뜻) 이 메소드는 어떤 http.Server 인스턴스와도 동작하고, 따라서 Connectand 나 Express 서버들과도 잘 동작한다.
req
객체는 다음과 같은 것들을 담을 수 있다:
url
: 요청 URLtimeout
: 밀리초 단위의 타임아웃method
: HTTP 메소드 (GET/POST)data
: 요청 바디(request body)headers
: 헤더 객체
res
객체는 단정문을 위한 응답을 받는 콜백 함수가 될 수도 있고, 다음과 같은 프로퍼티를 가진 객체로써 응답(response)에 대해 몇몇 단정문을 실행하는 데에 쓰일 수도 있다.
body
: 응답 몸체(response body)에 대한 단정문을 수행한다. 정규식이나 문자열이 될 수 있다.status
: 응답 상태코드(status code)에 대한 단정문을 수행한다.header
: 헤더항목들의 일치여부를 확인한다. (지정되지 않은 헤더 항목은 무시. 정규식, 혹은 문자열 사용가능)
res를 넘길때 추가적인 단정문 처리를 위해 네번재 인자로 콜백함수를 넘길 수 있다.
아래 코드는 위에서 설명한 내용에 대한 예제이다.
assert.response(server, {
url: '/', timeout: 500
}, {
body: 'foobar'
});
assert.response(server, {
url: '/',
method: 'GET'
}, {
body: '{"name":"tj"}',
status: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
'X-Foo': 'bar'
}
});
assert.response(server, {
url: '/foo',
method: 'POST',
data: 'bar baz'
}, {
body: '/foo bar baz',
status: 200
}, 'Test POST');
assert.response(server, {
url: '/foo',
method: 'POST',
data: 'bar baz'
}, {
body: '/foo bar baz',
status: 200
}, function(res){
// 모두 완료. 필요하다면 추가적인 테스트 수행
});
assert.response(server, {
url: '/'
}, function(res){
assert.ok(res.body.indexOf('tj') >= 0, 'Test assert.response() callback');
});
response 단정문 함수는 응답이 없거나 타임아웃(기본 30초)이 발생하면 실패로 간주한다.
expresso(1)
단일 테스트 스위트(=파일)을 실행하려면 다음과 같이
$ expresso test/a.test.js
몇몇 테스트 스위트들을 추가하길 원할 경우에는 다음과 같은 식으로
$ expresso test/a.test.js test/b.test.js
모든 테스트 스위트 안에서 특정한 것들만을 지정해서 테스트 하고 싶다면, 즉 화이트리스트(whitelist)테스트를 하고 싶으면 다음과 같이 수행한다.
$ expresso --only "foo()" --only "bar()"
혹은, 다음과 같은 식으로 한번에 호출도 가능하다.
$ expresso --only "foo(), bar()"
와일드카드 사용(Globbing)도 가능하다.
$ expresso test/*
아무것도 지정하지 않고 expresso를 호출하면 기본적으로는 test/* 를 호출한다. 따라서 아래 명령은 바로 위의 명령과 동일하다.
$ expresso
만약 테스트 실행전에 require.paths에서 지정된 패스를 변경하고 싶다면, -I (<=대문자 아이)
혹은 --include
옵션을 사용합니다.
$ expresso --include lib test/*
앞선 예제는 제가 추천하는 전형적인 예제입니다. expresso는 expresso에 번들되어 있는 node-jscoverage 를 통한 테스트 커버리지를 지원합니다. (테스트 커버리지 까지!! 쏘~쿨~~!!) 사용하려면 라이브러리의 인스트루먼트(instrumented) 버전을 노출해야만 합니다.
라이브러리를 인스트루먼트 하려면, 소스(src)와 대상(dest) 디렉터리를 node-jscoverage의 인자로 전달해 실행하면 됩니다.
$ node-jscoverage lib lib-cov
이제 다시 커버리지 구문들이 인스트루먼트된 lib-cov 디렉터리를 대상으로 테스트를 수행할 수 있습니다.
$ expresso -I lib-cov test/*
결과는 다음과 같을 겁니다. 물론 당신이 만든 테스트 커버리지에 따라 다르겠지요. :)
이 일련의 작업을 좀 더 쉽게 만들기 위해 expresso는 -c 혹은 --cov 옵션을 갖고 있습니다. 위에서 말한 두 단계 명령어와 동일한 일을 수행합니다. (-_-) 다음 두 명령어는 동일한 테스트를 실행합니다. 하지만 하나는 자동으로 인스트루먼트 되고, lib-cov 경로로 변경해서 실행될 것이고, 다른 하나는 그냥 테스트만 실행할 겁니다. (커버리지 만드는 건 당연 아래쪽이겠죠?)
$ expresso -I lib test/*
$ expresso -I lib --cov test/*
현재 테스트 커버리지는 lib 디렉터리에 있습니다. 하지만 향후에 --cov 옵션에 패스를 지정하게 될 가능성이 급니다.
만약 코드 커버리지 보고서를 자동 파싱하고 싶다면 --json [출력파일] 옵션을 사용하면 됩니다.
$ expresso -I lib test/*
$ expresso -I lib --cov --json coverage.json test/*
json 구조로 만들어진 테스트 커버리지 보고서를 볼 수 있을 겁니다.
{
"LOC": 20,
"SLOC": 7,
"coverage": "71.43",
"files": {
"bar.js": {
"LOC": 4,
"SLOC": 2,
"coverage": "100.00",
"totalMisses": 0
},
"foo.js": {
"LOC": 16,
"SLOC": 5,
"coverage": "60.00",
"totalMisses": 2
}
},
"totalMisses": 2
}
비동기 익스포트 (Async Exports)
콜백이나 특정 이벤트가 발생하기 전까지 테스트 수행을 지연시킬 필요가 있습니다. exports.foo = function() {}; 구문이 이럴때 사용됩니다.
setTimeout(function() {
exports['test async exports'] = function(){
assert.ok('wahoo');
};
}, 100);
익스포팅 시에 한 번 딱 실행됩니다. 해당 루핑내에서 첫번째 export 시점에 모든 테스트 함수들을 익스포트 해야 합니다. 이렇게하면 이미 export한 객체에 더이상 테스트를 추가할 수 없다는 뜻입니다. (무슨 소린지..잘 이해가... -_-;;)
첨언!
중간에 서버를 테스트 하는 부분에 대한 테스트 코드 관련 이야기입니다.
우리 nodepad 예제에서 해당 테스트 코드를 쓰려면 물론 테스트 코드 로직 자체를 조금 수정해야 합니다. 하지만 그전에 먼저 해주어야 할 것이 있습니다. 예제에서 생략된 부분을 채우면 다음과 같은 식으로 server.test.js가 작성되어야 합니다.
== 이하는 nodepad 하위 디렉터리인 test 디렉터리에 위치하는 server.test.js 파일이라 가정 ==
var appserver = require('../app.js');
var server = appserver.app;
exports.testAsync = function(beforeExit, assert) {
assert.response(server, {
url: '/', timeout: 500
}, {
...
(이하생략)
이런경우 app.js 파일에서 아래 부분처럼 리스닝 부분을 if로 감싸는 식으로 변경하면 됩니다.
if (!module.parent) {
app.listen(3000);
}
다른 모듈에 의해 포함될 경우 괄호안의 로직을 제외하는 코드입니다.
Python 해보신 분들은 바로 아하! 하실겁니다
네 그렇습니다. python의 if __name__ == "__main__" 구문과 유사한 컵셉이라고 보시면 되겠습니다. :D
'node.js (OctoberSkyJs)' 카테고리의 다른 글
[node.js 따라배우기 03] RESTful 메소드와 테스팅 (2) | 2011.11.03 |
---|---|
[Node.js 따라배우기] 그래서, Node.js는 무엇인가? (3) | 2011.11.02 |
[node.js 따라배우기 02] 설치와 애플리케이션 뼈대 만들기 (Installation and Skeleton App) (4) | 2011.10.31 |
Social Study - node.js 소개 동영상 함께 보기 후기 (1) | 2011.10.30 |
[node.js 따라배우기 01] Webapp을 만들어 봅시다! : 노드패드(Nodepad) (6) | 2011.10.28 |