프로트앤드 개발을 하면서 테스트는 어떻게 할 수 있는지 알아본다. 앵귤러에서는 테스트 관련 내용까지도 포함하고 있어서 개발 테스트의 편의성을 함께 제공하고 있다.
사전 준비
generator-angular를 설치하게 되면 앵귤러 프레임워크에 카르마(Karma) 테스트 프레임워크와 Mock 객체등이 포함되어 있다. 과거에는 Karma를 통해서 단위(Unit) 테스트와 E2E (End-To-End) 테스트 두가지를 하였으나, 최근에는 Karma로는 단위 테스트만을 수행하고 Protractor라는 새로운 테스트 프레임워크를 통해서 E2E 테스트를 수행한다
- karma.conf.js : Unit Test 환경파일. 내부적으로 Jasmine (http://pivotal.github.io/jasmine/) 프레임워크를 사용한다
- karma-e2e.conf.js : E2E Test 환경파일 (
- "grunt test" 를 통해 수행
Karma가 Jasmine 테스트 프레임워크를 사용하고, 필요한 files 목록을 정의하며 Chrome 브라우져에서 8080 포트를 사용하여 테스트함. angular.js 와 angular-mocks.js 파일은 필 첨부 (angular-mocks.js 안에 module, inject 펑션 존재)
// karma.conf.js
// Karma configuration
// http://karma-runner.github.io/0.10/config/configuration-file.html
module.exports = function(config) {
config.set({
// base path, that will be used to resolve files and exclude
basePath: '',
// testing framework to use (jasmine/mocha/qunit/...)
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'app/bower_components/angular/angular.js',
'app/bower_components/angular-mocks/angular-mocks.js',
'app/bower_components/angular-resource/angular-resource.js',
'app/bower_components/angular-cookies/angular-cookies.js',
'app/bower_components/angular-sanitize/angular-sanitize.js',
'app/bower_components/angular-route/angular-route.js',
'app/scripts/*.js',
'app/scripts/**/*.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
// list of files / patterns to exclude
exclude: [],
// web server port
port: 8080,
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Start these browsers, currently available:
// - Chrome
// - ChromeCanary
// - Firefox
// - Opera
// - Safari (only Mac)
// - PhantomJS
// - IE (only Windows)
browsers: ['Chrome'],
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun: false
});
};
Gruntfile.js 의 test Task 내역 : grunt test
// Test settings
karma: {
unit: {
configFile: 'karma.conf.js',
singleRun: true
}
}
... 중략 ..
grunt.registerTask('test', [
'clean:server',
'concurrent:test',
'autoprefixer',
'connect:test',
'karma'
]);
1. Jasmine 테스트 프레임워크
describe ~ it 으로 BDD를 표현한다. BDD는 TDD의 2세대 버전 정도로 이해하자. 보다 자세한 사항은 위키 참조 (http://en.wikipedia.org/wiki/Behavior-driven_development)
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
});
describe로 시작하는 것을 Suite (슈트)라고 부르고, it 으로 시작하는 것을 spec (스펙)이라고 부른다. 슈트안에 여러개의 스펙이 있을 수 있다. beforeEach(), afterEach()를 통해서 스펙 수행시 마다 환경 초기값 셋업(Setup)이나 마무리 작업(TearDown)을 수행할 수 있다. 스펙 안에는 expect 기대값에 대한 다양한 메서드를 제공한다.
toEqual()
toBe(), not.toBe()
toBeTruthy(), toBeFalsy()
toContain(), not.toContain()
toBeDefined(), toBeUndefined()
toBeNull()
toBeNaN()
toBeGreaterThan()
toBeLessThan()
toBeCloseTo() : 소수점
toMatch() : 정규표현식
toThrow()
...
보다 자세한 사항은 Jasmine 문서를 참조한다 (http://jasmine.github.io/2.0/introduction.html)
2. 앵귤러 Service 테스트 준비
yo를 통하여 앵귤러 코드를 완성하게 되면 자동으로 테스트 코드까지 생성된다. SessionService에 대한 테스트 코드는 /test/spec/services 폴더 밑에 동일한 session-service.js 파일이 존재한다. module 호출 후에 inject 가 올 수 있으나 inject 후에 module 호출은 올 수 없다. _ (underscore)를 양쪽에 붙이는 것은 angular프레임워크에 테스트를 위하여 해당 서비스를 사용함을 알려준다.
describe('Service: SessionService', function () {
// load the service's module
beforeEach(module('meanstackApp'));
// instantiate service
var SessionService;
beforeEach(inject(function (_SessionService_) {
SessionService = _SessionService_;
}));
it('should do something', function () {
expect(!!SessionService).toBe(true);
});
});
grunt test 명령을 수행하면 에러가 발생할 것이다. 그것은 index.html에 필요에 의해 추가한 .js파일을 Karma 환경파일에 추가하지 않았기 때문이다. controller 쪽에서도 에러가 발생하는데 기본 테스트 코드에서 발생하므로 불필요한 코드를 삭제한다.
// karma.conf.js
files: [
'app/bower_components/jquery/jquery.js',
'app/bower_components/bootstrap/dist/js/bootstrap.js',
'app/bower_components/angular/angular.js',
'app/bower_components/angular-mocks/angular-mocks.js',
'app/bower_components/angular-resource/angular-resource.js',
'app/bower_components/angular-cookies/angular-cookies.js',
'app/bower_components/angular-sanitize/angular-sanitize.js',
'app/bower_components/angular-route/angular-route.js',
'app/bower_components/angular-bootstrap/ui-bootstrap-tpls.js',
'app/bower_components/angular-ui-router/release/angular-ui-router.js',
'app/scripts/*.js',
'app/scripts/**/*.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
],
singleRun: true
// Controller 테스트 파일에서 불필요한 하기 코드 삭제
it('should attach a list of awesomeThings to the scope', function () {
expect(scope.awesomeThings.length).toBe(3);
});
3. 앵귤러 Service 테스트를 위한 Mock 사용
SessionService 테스트 수행전에 공통적으로 사용하고 있는 Factory나 Service에 대한 테스트 코드를 작성한 후 SessionService를 최종 테스트 한다
- msRequestFactory : 원하는 JSON 객체가 생성되는지 테스트
- msRestfulApi : $httpBackend 이용하여 요청 처리 테스트
- SessionService : $httpBackend 이용하여 요청 처리하고 SessionInfo의 정보 유무 테스트
msRequestFactory 통하여 만들어진 JSON 객체가 원하는 key:value 값을 가지고 있는지 확인한다. expect().toEqual()을 사용한다
'use strict';
describe('Service: msRequestFactory', function () {
// load the service's module
beforeEach(module('meanstackApp'));
// instantiate service
var msRequestFactory;
beforeEach(inject(function ($injector) {
msRequestFactory = $injector.get('msRequestFactory');
}));
it('should exist', function () {
expect(!!msRequestFactory).toBe(true);
});
it('should make JSON', function () {
var request = msRequestFactory.createRequest('user', 'login', '');
expect(request).toEqual({
'area' : 'user',
'resource' : 'login',
'id' : '',
'request' : {}
});
});
});
msRestfulApi는 $resource를 통해 서버에 요청을 보낸다. 서버 요청에 대한 Mock을 만들어서 응답을 줄 수 있는 기능으로 앵귤러는 $httpBackend를 제공한다. 즉, 실서버가 없을 경우 XHR 또는 JSONP 요청없이도 실서버에 요청한 것과 같은 응답을 줄 수 있다. $httpBackend의 유형은 두가지 이다
- request expectation : 요청 JSON 데이터의 검증 원하는 결과가 왔는지 설정. expect().respond()
- backend definition : 가상의 응답정보를 설정. when().respond()
서버의 요청은 모두 async 처리가 되므로 테스트코드 작성하기가 애매하다. 따라서 $httpBackend.flush()를 호출하는 시점에 request가 전달되므로, 테스트 코드 작성시 적절한 위치에 놓아야 한다. API 참조 (http://docs.angularjs.org/api/ngMock.$httpBackend)
// 로그인 서비스 테스트에서 계속 에러가 발생하여 Re-Thinking 중이다.
// 에러 코드
'use strict';
describe('Service: msRestfulApi', function () {
// load the service's module
beforeEach(module('meanstackApp'));
// instantiate service
var msRestfulApi, httpBackend, scope, resource;
beforeEach(inject(function ($injector) {
msRestfulApi = $injector.get('msRestfulApi');
httpBackend = $injector.get('$httpBackend');
scope = $injector.get('$rootScope');
resource = $injector.get('$resource');
httpBackend.when('POST', '/api/v1/user/login').respond(200, {'id': 1});
}));
it('should call test', function() {
var request = {
'area' : 'user',
'resource' : 'login',
'id' : '',
'request' : {}
};
msRestfulApi.login(request, function(response){
expect(response.id).toBe(1);
console.log(response);
}, function(error){
console.log(error);
});
httpBackend.flush();
expect(r.id).toBe(1);
});
});
<참조>
'AngularJS > Start MEAN Stack' 카테고리의 다른 글
[MEAN Stack] Protractor 통한 클라이언트 E2E Test (0) | 2014.06.03 |
---|---|
[MEAN Stack] 다국어 처리하기 (1) | 2014.05.28 |
[MEAN Stack] 로그인 서비스 개발 및 정보 저장하기 (0) | 2014.02.04 |
[MEAN Stack] Yo 이용하여 Controller 생성하고 UI-Router에 설정하기 (0) | 2014.01.30 |
[MEAN Stack] 메뉴 구성하고 UI Routing 설정하기 (0) | 2014.01.29 |