[책] 자바스크립트 패턴과 테스트

역할모델과 객체망

테스트를 작성하는데 있어 다양한 관점이 존재합니다.
테스트를 단지 정상적인 기능이 작동함을 확인하는데 사용할 수도 있고 보다 견고한 작동을 위해 다양한 예외상황에 대한 대처를 확인할 때도 사용할 수 있죠.

하지만 좀 더 세밀하게 들어가보면 하나의 문제를 해결하는 거대한 함수나 객체는 수정이 어렵기 때문에 역할을 기반으로 분할하고 분할된 역할끼리 협력하여 문제를 해결하게 합니다.
이런 경우 가장 마지막에 기능을 처리하는 말단은 몇 개 되지 않기 때문에 정상기능 중인지를 테스트하는 단위테스트는 전체 테스트 중에 극히 일부만 작성하면 됩니다.
대부분은 여러 역할끼리 제대로 협력하고 있는지, 그에 맞게 통신을 제대로 하고 있는지를 확인하는 것입니다
만약 객체지향으로 개발했다면 각 객체가 다른 객체와 바른 방법으로 통신(메소드호출)을 적절한 메세지(인자)로 하는지 확인하는 것이 태반의 테스트가 됩니다.

예를 들어 로그인을 처리하는 모듈이라면

  1. 밸리데이션을 처리한 뒤
  2. 디비로부터 기존 정보를 취합하여
  3. 적합한 요청인지를 확인하여
  4. 로그인의 절차를 시행한다

라고 간단히 요약해볼 수 있을 것입니다. 그렇다면 객체망에 참가하는 역할은 LogInValidator, LogInInfo, LogInRequest, LogInProcessor 로 정의해볼 수 있고, 이 모든 객체를 포괄하는 미디에이터가 LogIn이 되는 식으로 생각해볼 수 있을 것입니다.

var LogIn = function(validator, info, request, processor){

  //각 역할을 로딩한다.
  this.validator = validator;
  this.dbInfo = info;
  this.request = request;
  this.processor = processor;

};

LogIn.prototype.login = function(request){

  //밸리데이션처리
  if(!this.validator.isValid(request)) return false;
  
  //디비정보취득
  var info = this.dbInfo.getData(request);
  
  //요청의 적합성 판정
  if(!this.request.isValid(info)) return false;

  //로그인 절차 진행
  return this.processor(info);
};

// 실제 사용
var login = new LogIn(
  new LogInValidator(),
  new LogInInfo(),
  new LogInRequest(),
  new LogInProcessor()
);
if(!login.login({id:'hika', pw:'1111'})) alert('실패');

실제 테스트해야 할 대상

이러한 객체 망의 통신에서 실제 우리가 LogIn의 login메소드에서 테스트해야할 것은 각각의 역할을 수행하는 객체에 제대로된 메세지를 전달해주는가 입니다.
상세하게 정의된 객체망은 자기의 역할을 다시 다른 세부적인 역할을 수행하는 객체나 함수로 위임하기 때문에 대부분의 코드는 메세지의 가공과 전달에 있습니다.
이럴 때 내부를 직접 건드리지 않고 외부에서 공급될 객체에 감시를 붙이는 방식이 많이 사용됩니다.
보통 DI를 이용한 객체 조립기를 사용하는 경우 의존성 관계를 역추적하여 가장 말단부터 생성하여 조립해주는데 위의 경우 다음과 같이 간단히 정의해볼 수 있을 것입니다.

  1. 우선 validator, info, request, processor 객체를 생성한뒤
  2. login을 생성할때 이를 넣어준다.

직접 코드에서 하지 않고 조합기에게 명령을 내리면 되는데 위의 레시피에 맞게 json으로 간단히 만들어보죠.

[
  {key:'validator', command:'new', constructor:LogInValidator, arg:[]},
  {key:'info',      command:'new', constructor:LogInInfo, arg:[]},
  {key:'request',   command:'new', constructor:LogInRequest, arg:[]},
  {key:'processor', command:'new', constructor:LogInProcessor, arg:[]},
  {key:'login',     command:'new', constructor:LogIn, arg:[
     DI.arg('validator'),
     DI.arg('info'),
     DI.arg('request'),
     DI.arg('processor')
  ]}
]

복잡한 의존성 관리를 해주면 더 좋겠지만, 간단한 예시이므로 이해하기 쉽게 배열로 의존성 순서를 정해줬고 마지막에 DI컨테이너에 있는 객체를 넣기 위해 DI.arg(..)함수로 값을 지연시켜둡니다.
이런 경우 실제 사용되는 LogIn은 DI로 부터 얻어집니다.

var login = DI.get('login'); //DI로부터 얻는다.

if(!login.login({id:'hika', pw:'1111'})) alert('실패');

이제 코드에서 객체 생성에 대한 의존성을 제거했기 때문에 DI레벨에서 다양한 테스트더블을 이용한 통신감시가 가능합니다. 즉 기존에

{
  key:'validator', 
  command:'new', 
  constructor:LogInValidator, 
  arg:[]
}

이렇게 공급되던 validator를 테스트더블로 교체한다면,

{
  key:'validator', 
  command:'new', 
  constructor:FakeLogInValidator, //가짜로 교체
  arg:[]
},

위와 같이 감시 가능한 객체를 제공하던가 아니면 자스민같은 테스트더블 프레임웍을 이용해 생성해도 되겠죠.

{
  key:'validator', 
  target:spyOn(new LogInValidator(), 'isValid').and.returnValue(true), //자스민스파이
  arg:[]
},

따라서 작성된 테스트코드는 기능이 정상적으로 작동하는가를 테스트하는게 아니라 결국 객체통신이 바르게 이루어졌는가를 검사하게 됩니다.

describe('로긴시 내부객체 통신점검', _=>{

  //로긴요청 데이터
  var request = {id:'hika', pw:'1111'};

  //로긴을 호출했다면..
  DI.get('login').login(request);

  //밸리데이터의 isValid가 호출되어야한다.
  it('validator의 isValid호출', _=>expect(DI.get('validator').isValid).toHaveBeenCalledWith(request));

});

객체망을 테스트하기 위해서는 반드시 테스트더블의 도움이 필요하고 더 나아가 지저분한 코드작성을 피하려면 DI도 함께 사용하는게 좋습니다.

다시 책으로 돌아와서

이 책은 자스민을 이용하여 테스트코드를 기술하고 있는데,

  1. 객체 망의 교과서와 같은 고전 디자인패턴과
  2. 자바스크립트쪽에서 사용하는 다양한 언어패턴,
  3. 프라미스 등의 현대화된 구조패턴에 대해

어떻게 객체역할모델을 테스트할지를 다루고 있습니다. 사실 다른 언어용 테스트관련 책에서는 많이 다루던 내용이지만 자바스크립트용 책으로 심도있는 객체망 테스트를 주제로 다룬다는 것이 인상적입니다.
단지 원래 책의 난이도가 어려워 중급 정도 되는 면도 있습니다만, 저자가 다소 불친절하게 내용을 생략하면서 전개하거나 의도가 일부 코드 상으로 잘못 표현된 경우도 있는 등, 저자의 실력보다 원고가 좀 대충 나온 느낌이랄까, 그런 면도 있습니다.

하지만 복잡한 자바스크립트 애플리케이션의 개발과 유지보수에 큰 도움을 줄 수 있는 개발 방법론을 교과서적으로 수 많은 예시와 함께 제시한다는 점은 굉장히 추천할만한 점이라 하겠습니다.

나름대로 책을 읽는 요령은?

이 책은 사실 기저에 독자가

  1. 어느 정도 소프트웨어 설계에 대해 익숙하고,
  2. 디자인패턴이나 SOLID원칙을 숙지하고 있는 레벨을 넘어 잘 이해하고 있다고 가정하는 면이 있으며
  3. 동시에 함수형 패러다임과 중첩된 스코프와 동적인 this의 컨텍스트를 자유롭게 사용할 수 있다는

전재 하에 내용을 전개하고 있습니다. 따라서 본질적인 의도나 의미를 알아차릴려면 선행학습이 많이 필요한 편입니다.
또한 테스트프레임웍으로 자스민을 사용하는데 태반 테스트더블을 심도있게 사용하는 내용으로 구성되어 있어

  1. 자스민 테스트 프레임웍과 기능에 대한 자체의 이해 뿐만 아니라
  2. BDD개념과 객체망을 구성하는 능력 및
  3. 각 객체 간의 협력모델을 테스트로 표현할 수 있는 사고력을 요구합니다.

즉 내가 원하는 코드를 테스트로 표현한다면? 이라는 식으로 내용이 전개됩니다. 진정한 테스트주도개발이라 할 수 있죠.
제가 개인적으로 추천하는 책을 보는 요령이라면,

  1. 각 장에서 우선 자스민 테스트코드 부분만 보면서,
  2. 테스트하고 있는 대상 객체가 어떻게 생겼는지를 추측하여
  3. 코드로 작성해보라는 것입니다.
  4. 그 이후에 뒤를 보고 실제 추측한 대로 테스트 했던 대상의 코드가 일치하는지 확인하는 식으로 학습한다면

“내가 작성하고 싶은 코드를 어떻게 테스트로 표현할 수 있는지”에 대한 감각이 크게 향상될 수 있습니다.

기타

실은 이 책으로는 온라인으로 이미 스터디를 전개해서 종료되었습니다.

[스터디] o2 자바스크립트 패턴과 테스트

온라인 스터디의 녹화분은 다음의 유튜브 채널에 공개되어있습니다.

또한 관심이 가신다면 아래 테스트관련 책도 추천해드립니다.