Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

should.js : node.js에서 사용할 수 있는 BDD 스타일의 Assertion 모듈

몇일 전에 mocha 테스트 프레임워크를 소개한 관계로 mocha에서 사용할 수 있는 assetion 모듈인 should.js를 정리해 봅니다. mocha 글에서도 썼듯이 mocha는 꼭 should.js를 써야하는 것은 아니지만 사용법도 깔끔하고 개인적으로 mocha와 should.js를 만든 TJ Holowaychuk에 대한 신뢰도 있기 때문에 should.js를 사용하고 있습니다.(이러다 LearnBoost 빠돌이 되겠;;;) assertion 모듈이라는 것이 간단한 메서드와 인터페이스만 파악하면 큰 내용은 없기 때문에 정리라고 하기 보다는 should.js의 문서를 그대로 옮겨놓는 정도이지만 그래서 오히려 문서를 찬찬히 본적도 없기에 한번 찬찬히 보는 셈 치고 정리합니다.




should.js

should.jsnode.js에서 사용할 수 있는 표현적이고 가독성있으며 테스트 프레임워크에 의존적이지 않은 단언문(assertion) 라이브러리입니다. 객체의 행동을 enumerable않은 단일 getter를 통해 Object를 prototype 확장해서 제공하고 있습니다.

그리고 사실 should.js는 node.js에서 제공하는 Assert 모듈을 확장했기 때문에 assert 모듈과 동일합니다. 즉 should.equal(str, 'foo')는 assert.equal(str, 'foo')와 똑같이 동작하고 should.AssertionError는 assert.AssertionError와 같습니다. 그래서 node.js의 assert 모듈을 지원하는 어떤 테스트 프레임워크에서도 should.js를 사용할 수 있습니다.

should.js는 소스에서 사용해야 하므로 다음과 같이 로컬설치를 합니다.

$ npm install should

이후의 예제는 소스에서 var should = require('should');로 불러왔다는 가정하에 설명합니다.



should.js의 추가 메서드
앞에서 얘기했듯이 require('should')가 리턴한 객체는 require('assert')가 리턴한 객체를 확장한 것과 같고 assertion을 더 표현적이고 가독성 좋게 하는 추가적인 메서드들을 제공하고 있습니다.


exist (정적)

should.strictEqual(foo, bar)

foo.should.equal(bar)

위 두 코드는 동일합니다. should.strictEqual(foo, bar)는 assert.strictEqual(foo, bar)와 동일합니다. 하지만 should는 Object를 prototype확장했기 때문에 2번라인처럼 작성할 수 있습니다. 이는 더 가독성있게 만들어 줍니다. 하지만 위에서 foo가 존재하지 않는 경우에는 foo is null or undefined이라는 오류가 발생할 것입니다. 객체나 프로퍼티의 존재를 알 수 없는 경우에는 이와 같이 사용할 수 없습니다. 객체나 프로퍼티의 존재 여부는 다음과 같이 exist 메서드로 확인할 수 있습니다.


should.exist({})
should.exist([])
should.exist('')
should.exist(0)
should.exist(null)      // will throw
should.exist(undefined) // will throw


다음과 같이 not을 사용해서 부정문으로 사용할 수도 있습니다.


should.not.exist(undefined)
should.not.exist(null)
should.not.exist('')    // will throw
should.not.exist({})    // will throw


이렇게 객체의 존재여부를 확인한 뒤에는 맘편히 객체에 should를 사용할 수 있습니다.


ok
assertion의 진실성(?)은 ok로 검사합니다. 이는 소스 내부에서 그냥 전달한 객체 obj로만 검사합니다. obj == true가 아니라 그냥 obj로 검사한다는 의미입니다.

true.should.be.ok
'yay'.should.be.ok
(1).should.be.ok

false.should.not.be.ok
''.should.not.be.ok
(0).should.not.be.ok



true / false
true와 false는 다음과 같이 사용합니다. 이는 Assert === true나 Assert === false로 검사합니다.


true.should.be.true
'1'.should.not.be.true

false.should.be.false
(0).should.not.be.false



arguments
arguments는 Arguments의 인스턴스인지를 검사합니다.

var args = (function(){ return arguments; })(1,2,3);
args.should.be.arguments;
[].should.not.be.arguments;



empty
empty는 객체의 length가 0인지 검사합니다.


[].should.be.empty
''.should.be.empty
({ length: 0 }).should.be.empty



eql

두 객체가 동등한지 검사합니다. 단순값은 ==로 검사하고 객체일 경우에는 내부 속성을 ===로 검사합니다.

({ foo: 'bar' }).should.eql({ foo: 'bar' })
[1,2,3].should.eql([1,2,3])




equal
strict 하게 동등성을 비교합니다. 비교는 ===로 검사합니다.

should.strictEqual(undefined, value)
should.strictEqual(false, value)
(4).should.equal(4)
'test'.should.equal('test')
[1,2,3].should.not.equal([1,2,3])




within
숫자의 범위를 포함하는지 검사합니다.

user.age.should.be.within(5, 50)




a
객체의 typeof를 검사합니다.

user.should.be.a('object')
'test'.should.be.a('string')




instanceof
instanceof를 검사합니다.

user.should.be.an.instanceof(User)
[].should.be.an.instanceof(Array)




above

주어진 값이 지정한 숫자 값보다 큰 지를 검사합니다.

user.age.should.be.above(5)
user.age.should.not.be.above(100)




below
주어진 값이 지정한 숫자 값보다 작은 지를 검사합니다. regexp.exec로 검사합니다.

user.age.should.be.below(100)
user.age.should.not.be.below(5)




match
정규표현식의 match 여부를 검사합니다.

username.should.match(/^\w+$/)




length
length 프로퍼티가 존재하는지 검사하고 주어진 숫자값인지를 검사합니다. lengthOf는 length의 별칭입니다.

user.pets.should.have.length(5)
user.pets.should.have.a.lengthOf(5)




property
property에 첫 파라미터로 전달한 이름의 프로퍼티가 존재하는지 검사하고 두 번째 파라미터를 전달하면 해당 프로퍼티가 두 번재 파라미터 값인지 검사합니다.

user.should.have.property('name')
user.should.have.property('age', 15)
user.should.not.have.property('rawr')
user.should.not.have.property('age', 0)




ownProperty
prototype 속성이 아닌 객체 자체가 가진 속성이 존재하는지 검사합니다. 검사는 obj.hasOwnProperty(name)로 수행합니다.

({ foo: 'bar' }).should.have.ownProperty('foo')




status(code)
객체가 statusCode 프로퍼티를 가지고 있는지 검사하고 statusCode 프로퍼티가 code값인지를 검사합니다.

res.should.have.status(200);




header(field[, value])

객체가 headers 프로퍼티가 존재하는지 검사하고 field로 지정한 프로퍼티가 존재하는지 검사합니다. value를 전달하면 field 프로퍼티가 value값인지 검사합니다.

res.should.have.header('content-length');
res.should.have.header('Content-Length', '123');
res.should.have.header('content-length', '123');




json
Content-Type가 "application/json; charset=utf-8"인지 검사합니다.

res.should.be.json




html
Content-Type가 "text/html; charset=utf-8"인지 검사합니다.

res.should.be.html




include(obj)
전달한 obj가 indexOf()로 나타낼 수 있는지 검사합니다. 그래서 문자열과 배열이나 indexOf를 구현한 객체에서 사용할 수 있습니다.

// 배열
[1,2,3].should.include(3)
[1,2,3].should.include(2)
[1,2,3].should.not.include(4)

// 문자열
'foo bar baz'.should.include('foo')
'foo bar baz'.should.include('bar')
'foo bar baz'.should.include('baz')
'foo bar baz'.should.not.include('FOO')




includeEql(obj)
배열에서 사용할 수 있으면 전달한 obj와 같은 객체가 있는지 검사합니다.

[[1],[2],[3]].should.includeEql([3])
[[1],[2],[3]].should.includeEql([2])
[[1],[2],[3]].should.not.includeEql([4])




throw([message])
예외가 던져지는 지 검사합니다.

(function(){
  throw new Error('fail');
}).should.throw();

(function(){

}).should.not.throw();



throw()에 message를 전달하면 예외 메시지가 message와 일치하는지 검사합니다. message에는 정규표현식을 사용할 수도 있습니다.

(function(){
  throw new Error('fail');
}).should.throw('fail');

(function(){
  throw new Error('failed to foo');
}).should.throw(/^fail/);




keys
전달한 키의 이름을 객체가 모두 가지고 있는지 검사합니다. 배열이나 파라미터로 전달할 수 있는데 객체에서 빠진 키가 있으면 실패합니다.

var obj = { foo: 'bar', baz: 'raz' };
obj.should.have.keys('foo', 'bar');
obj.should.have.keys(['foo', 'bar']);






체이닝과 오류 메시지
앞의 예제에서 보았듯이 assertion은 체이닝으로 연결할 수 있습니다.



user.should.have.property('pets').with.lengthOf(4)




이는 다음 코드와 근본적으로는 동일하지만 pet 프로퍼티가 존재하지 않을 수도 있습니다.




user.should.have.property('pets').with.lengthOf(4)





다음처럼 and같은 더미 getter를 사용하면 더 명확하게 체이닝할 수 있습니다.



user.should.be.a('object').and.have.property('name', 'tj')





assertion이 실패했을 때 더 명확한 내용을 보여주기 위해서 다음과 같이 각 매처에 추가적인 설명 파라미터를 전달하면 실패했을 경우 표시해줍니다. 이 추가적인 설명파라미터는 eql, equal, within, a, instanceof, above, below, match, length, property, ownProperty, include, includeEql 에서 사용할 수 있습니다.


(1).should.eql(0, 'some useful description')

AssertionError: expected 1 to equal 0 | some useful description
  at Object.eql (/Users/swift/code/should.js/node_modules/should/lib/should.js:280:10)
  ...



2012/04/21 05:00 2012/04/21 05:00