잊지 않겠습니다.


service (non-HTTP)

angularJS의 service code는 일반적으로 REST API에 대한 호출을 하고, 그 결과를 받는 것을 담당하지만, 값의 변경이나 특정값들의 display 형식으로의 변경등을 service로 만들어서, 코드의 응집력을 높일 수 있습니다. 섭씨온도를 화씨온도로 바꾸는 등의 계산 로직도 역시 이 영역에 들어가게 됩니다.

이러한 service code의 테스트는 다음 항목을 확인하면 됩니다.

input 값에 대하여 output 값이 예상대로 나왔는지. 
> toBe(value) 구문을 이용합니다.

기본적으로 matcher를 사용하는 것이 point입니다. 예상한 값을 기준으로 원하는 output이 정상적으로 나오는지를 확인하는 것이 좋습니다.

service (with HTTP)

REST API를 사용하는 service code의 테스트 방법에 대해서 알아보도록 하겠습니다. 일단 REST API를 호출한다면 외부의 HTTP resource를 이용하게 됩니다. 외부의 자원을 이용한다는 것은 그 외부의 자원에 대한 통제권은 우리가 개발한 application에는 없다는 말과 동일합니다. 따라서, 우리가 REST API를 이용하는 service에서 확인할 내용은 다음 두가지입니다.

* 원하는 URL로 정상적으로 호출하고 있는지?
* 전달하는 parameter가 API 호출 규약에 맞는지?

여기서 두가지의 역활이 나뉘게 됩니다. 먼저 원하는 URL로 호출하는 것은 당연히 service가 하는 일입니다. 그런데, 전달하는 parameter가 호출규약에 맞는지는 어떻게 확인하는 것이 좋을까요. 우리가 Spring을 이용한 개발을 진행하였을때를 생각해보면 쉽게 결론이 나옵니다. Validator객체를 만들어서 사용하는 것입니다.

따라서, REST API를 호출하는 service의 코드는 두개의 객체로 나뉘어지면 테스트 및 개발이 원활하게 됩니다. 이와 같은 형태는 마치 Spring MVC에서의 Controller 객체와 Validator 객체를 따로 개발하는 것과 동일한 패턴을 가지고 오게 됩니다. 객체지향이라는 것이 궁극적으로는 서로간에 할 일을 명확히 구분지어주고 그 분류대로 일을 진행하는 것이니까요.

먼저, 간단한 validator code는 다음과 같습니다.

'use strict';

/**
 * @ngdoc service
 * @name fmsmobilewebApp.ResultRegisterValidatorService
 * @description
 * # ResultRegisterValidatorService
 * Service in the fmsmobilewebApp.
 */
angular.module('fmsmobilewebApp').service('ResultRegisterValidatorService', function ResultRegisterValidatorService() {
  var self = this;

  self.validateStatus = function(data) {
    return angular.isDefined(data.status);
  };

  self.validateDepartment = function(data) {
    return angular.isDefined(data.departmentId);
  };

  self.validateOpResult = function(data) {
    var checked = angular.isDefined(data.opResult) &&
                  angular.isDefined(data.opResult.content) &&
                  angular.isDefined(data.opResult.startDate) &&
                  angular.isDefined(data.opResult.endDate);
    return checked;
  };
});

그리고 이에 대한 테스트 코드는 다음과 같습니다.

'use strict';

describe('Service: ResultRegisterValidatorService', function () {

  // load the service's module
  beforeEach(module('fmsmobilewebApp'));

  // instantiate service
  var service;
  beforeEach(inject(function (_ResultRegisterValidatorService_) {
    service = _ResultRegisterValidatorService_;
  }));

  it('validate for opResult', function() {
    var data = {};
    expect(service.validateOpResult(data)).toBe(false);
    data.opResult = {};
    expect(service.validateOpResult(data)).toBe(false);
    data.opResult.content = 'Content';
    expect(service.validateOpResult(data)).toBe(false);
    data.opResult.startDate = '2014-04-05 12:00';
    expect(service.validateOpResult(data)).toBe(false);
    data.opResult.endDate = '';
    expect(service.validateOpResult(data)).toBe(true);
  });

  it('validate for status', function() {
    var data = {};
    expect(service.validateStatus(data)).toBe(false);
    data.status = '0701';
    expect(service.validateStatus(data)).toBe(true);
  });

  it('validate for department', function() {
    var data = {};
    expect(service.validateDepartment(data)).toBe(false);
    data.departmentId = '0401';
    expect(service.validateDepartment(data)).toBe(true);
  });

});

Validator의 test code의 경우에는 기본적인 service code와 동일합니다. input에 따른 결과가 어떻게 나오는지만 확인하면 됩니다.

이제 가장 중요한 http에 대한 서비스 코드입니다. 우리는 test code를 작성하기 전에 REST API를 호출하는 행위에 대한 정의가 우선 필요합니다.
REST API를 호출하는 것은 다음과 같습니다.

  1. URL에 대한 HTTP request
  2. method에 따른 HTTP request
  3. HTTP response에 대한 결과값에 대한 parsing

결국은 우리가 확인할 것은 위 3가지입니다. 그리고 중점을 줘야지 되는 것을 따지자면 1, 2에 대한 확인입니다. 3에 대한 내용은 REST API의 호출 특성상, API 서버측에서 보내는 결과이기 때문에 그 결과값이 json format인 경우, 우리가 테스트를 해서 확인할 내용이 아닙니다. 우리는 그 결과값을 온전히 받아서 사용하는 client측에서만 생각하면 되기 때문에 1, 2번에 대한 내용만을 테스트한다면 우리가 원하는 test code를 작성할 수 있습니다.

angularJS는 angular-mock을 통해 $httpBackend 객체에 우리가 호출되는 URL, method를 확인할 수 있는 방법을 제공합니다. 일단 다음 상황을 가정해보도록 합니다.

GET /api/as/list

그리고, 이러한 url을 호출하는 API Service를 작성합니다.

function AsService(Restangular, AsServiceValidator) {
  var self = this;
  self.getList = function(status, department, func) {
    if(AsServiceValidator.checkStatus(status) && AsService.checkDepartment(department)) {
      Restangular.all('/api/as/list').get().then(function(jsonResult) {
        if(jsonResult.ok && func) {
          func(jsonResult);
        }
      });
      return true;
    } 
    return false;
  };
}

위 코드는 그 전에 이야기한 validator에 대한 체크 후, Restangular를 이용한 http call을 하고 있습니다. service에 대한 test code를 작성할 때, 반드시 $httpBackend에 대한 expect를 설정해야지 됩니다. 다음은 test code의 일부분입니다.

var httpBackend, service
beforeEach(inject(function($httpBackend, AsService) {
  httpBackend = $httpBackend;
  service = AsService;
}));

위 코드패턴을 이용해서 우리는 테스트의 대상이 되는 service와 $httpBackend를 얻어올 수 있습니다. 이제 우리가 원하는 API를 정상적으로 호출하고 있는지를 확인해보도록 하겠습니다.

it('as list REST API 호출', function() {
  httpBackend.expectGET(/\/api\/as\/list/\W*).respond('{ ok: true, data: null, message: data }');
  service.getList(status, department, function(jsonResult) {
    console.log(jsonResult);
  });
  httpBackend.flush();
});

REST API를 호출하는 service의 test code pattern은 다음과 같습니다.

  1. expectGET/expectPOST/expectDELETE/expectPUT method를 이용해서 request가 전달될 URL을 예상합니다.
  2. service를 호출합니다.
  3. httpBackend.flush() method를 호출합니다.

여기서 주의할점은 httpBackend.flush() 입니다. flush가 호출되게 되면, httpBackend는 httpRequest에 대한 response를 발생시킵니다. 만약에 expect에 정의되지 않은 http request가 발생되게 되면 exception을 발생하게 됩니다. REST API의 response를 명확히 알고 있다면, 그 결과값에 대한 처리 로직도 테스트가 가능합니다.

REST API service에 대한 test code작성방법에 대해서 다시 한번 정의해보도록 하겠습니다.

  1. input에 대한 validator의 분리가 필요 > validator 테스트 코드
  2. $httpBackend.expectXXX method를 이용한 REST API 호출 확인

이 두가지에 중점을 주면 이제 service code 역시 테스트가 가능하게 됩니다.


Posted by xyzlast Y2K