- Unit 과 Midway를 테스트시에는 테스트 스팩은 Mocha(모카)를 이용하고 Matcher/Assertion으로 Chai를 사용한다
- E2E 테스트시에는 Mocah, Chai를 사용할 수 없었다. 별도의 E2E 수행 문서를 제공한다
- 테스트 시에 AngularJS는 XHR 요청도 Interceptors를 통해서 가로챌 수 있다.
그외 route, controller, service, directive까지도 가로채서 테스트 코드를 넣을 수 있다.
- XHR 가로채기의 비용이 크면 angular-mock.js를 통하여 Mock을 사용할 수도 있다
1. Testing Module
- AngularJS에서 모듈은 directives, controllers, templates, services 그리고 resources 생성을 위한 컨테이너 오브젝트이다
- 따라서 테스트에 최초로 할 것이 해당 모듈이 있는지 체크하는 것이다
- Midway 테스트에서 수행한다
- midway/appSpec.js
//// test/midway/appSpec.js//describe("Midway: Testing Modules",function(){describe("App Module:",function(){varmodule;before(function(){module=angular.module("App");});it("should be registered",function(){expect(module).not.to.equal(null);});
// App이 의존하는 모듈들이 있는지 검증
describe("Dependencies:",function(){vardeps;varhasModule=function(m){returndeps.indexOf(m)>=0;};before(function(){deps=module.value('appName').requires;});//you can also test the module's dependenciesit("should have App.Controllers as a dependency",function(){expect(hasModule('App.Controllers')).to.equal(true);});it("should have App.Directives as a dependency",function(){expect(hasModule('App.Directives')).to.equal(true);});it("should have App.Filters as a dependency",function(){expect(hasModule('App.Filters')).to.equal(true);});it("should have App.Routes as a dependency",function(){expect(hasModule('App.Routes')).to.equal(true);});it("should have App.Services as a dependency",function(){expect(hasModule('App.Services')).to.equal(true);});});});
//// test/midway/routesSpec.js//describe("Testing Routes",function(){vartest;before(function(done){test=newngMidwayTester();test.register('App',done);});it("should have a videos_path",function(){expect(ROUTER.routeDefined('videos_path')).to.equal(true);varurl=ROUTER.routePath('videos_path');expect(url).to.equal('/videos');});it("the videos_path should goto the VideosCtrl controller",function(){varroute=ROUTER.getRoute('videos_path');route.params.controller.should.equal('VideosCtrl');});it("the home_path should be the same as the videos_path",function(){expect(ROUTER.routeDefined('home_path')).to.equal(true);varurl1=ROUTER.routePath('home_path');varurl2=ROUTER.routePath('videos_path');expect(url1).to.equal(url2);});
});
- E2E Testing
// test/e2e/routesSpec.js//describe("E2E: Testing Routes",function(){beforeEach(function(){browser().navigateTo('/');});it('should jump to the /videos path when / is accessed',function(){browser().navigateTo('#/');expect(browser().location().path()).toBe("/videos");});it('should have a working /videos route',function(){browser().navigateTo('#/videos');expect(browser().location().path()).toBe("/videos");});it('should have a working /wathced-videos route',function(){browser().navigateTo('#/watched-videos');expect(browser().location().path()).toBe("/watched-videos");});it('should have a working /videos/ID route',function(){browser().navigateTo('#/videos/10');expect(browser().location().path()).toBe("/videos/10");});});
3. Testing Controllers
- $scope를 통하여 template 페이지와 데이터 바인딩 되는 테스트
- Unit Testing
// test/unit/controllers/controllersSpec.js//describe("Unit: Testing Controllers",function(){beforeEach(angular.mock.module('App'));it('should have a VideosCtrl controller',function(){
// controller 모듈이 있는지 검증
expect(App.VideosCtrl).not.to.equal(null);});it('should have a VideoCtrl controller',function(){expect(App.VideoCtrl).not.to.equal(null);});it('should have a WatchedVideosCtrl controller',function(){expect(App.WatchedVideosCtrl).not.to.equal(null);});
// $httpBackend Mock 객체를 통해 단위 테스트 검증it('should have a properly working VideosCtrl controller',inject(function($rootScope,$controller,$httpBackend){
response.respond(null);var$scope=$rootScope.$new();varctrl=$controller('VideosCtrl',{$scope:$scope,$routeParams:{q:searchTestAtr}});}));it('should have a properly working VideoCtrl controller', inject(function($rootScope,$controller,$httpBackend){varsearchID='cars';varresponse=$httpBackend.expectJSONP('https://gdata.youtube.com/feeds/api/videos/' +searchID+'?v=2&alt=json&callback=JSON_CALLBACK');response.respond(null);var$scope=$rootScope.$new();varctrl=$controller('VideoCtrl',{$scope:$scope,$routeParams:{id:searchID}});}));it('should have a properly working WatchedVideosCtrl controller',inject(function($rootScope,$controller,$httpBackend){
var$scope=$rootScope.$new();//we're stubbing the onReady event$scope.onReady=function(){};varctrl=$controller('WatchedVideosCtrl',{$scope:$scope});}));
// 라투트통하여 페이지 변경 성공 이벤트 발생하면 onChange() 호출
before(function(){test.$rootScope.$on('$routeChangeSuccess',function(){if(onChange)onChange();});});beforeEach(function(done){test.reset(done);});it('should load the VideosCtrl controller properly when /videos route is accessed',function(){onChange=function(){test.path().should.eq('/videos');varcurrent=test.route().current;varcontroller=current.controller;varscope=current.scope;expect(controller).to.equal('VideosCtrl');};test.path('/videos'); // /videos 호출되면 자동으로 onChange() 호출되어 검증 });it('should load the WatchedVideosCtrl controller properly when /watched-videos route is accessed',function(done){onChange=function(){test.path().should.eq('/watched-videos');varcurrent=test.route().current;varcontroller=current.controller;varparams=current.params;varscope=current.scope;expect(controller).to.equal('WatchedVideosCtrl');done();};test.path('/watched-videos');});});
// 첫 페이지 있는지 검증
beforeEach(function(){browser().navigateTo('/');});
// videos의 html 화면에서 DIV id="ng-view"의 html 내역 안에 data-app-youtube-listings이 있는지 검증
it('should have a working videos page controller that applies the videos to the scope',function(){browser().navigateTo('#/');expect(browser().location().path()).toBe("/videos");expect(element('#ng-view').html()).toContain('data-app-youtube-listings');});it('should have a working video page controller that applies the video to the scope',function(){browser().navigateTo('#/videos/WuiHuZq_cg4');expect(browser().location().path()).toBe("/videos/WuiHuZq_cg4");expect(element('#ng-view').html()).toContain('app-youtube-embed');});
});
4. Testing Services/Factories
- 서비스는 테스트가 가장 쉬운 코들 블럭이다
- Service와 Factory에 대해서 테스트 하는 방법
- Unit Testing
// test/unit/services/servicesSpec.js//describe("Unit: Testing Controllers",function(){beforeEach(angular.mock.module('App'));it('should contain an $appStorage service',inject(function($appStorage){expect($appStorage).not.to.equal(null);}));
// 서비스 존재 유무 검증
it('should contain an $appYoutubeSearcher service',inject(function($appYoutubeSearcher){expect($appYoutubeSearcher).not.to.equal(null);}));it('should have a working $appYoutubeSearcher service',inject(['$appYoutubeSearcher',function($yt){expect($yt.prefixKey).not.to.equal(null);expect($yt.resize).not.to.equal(null);expect($yt.prepareImage).not.to.equal(null);expect($yt.getWatchedVideos).not.to.equal(null);}]));
// $appYoutubeSearcher Factory 검증 it('should have a working service that resizes dimensions',inject(['$appYoutubeSearcher',function($yt){varw=100;varh=100;varmw=50;varmh=50;varsizes=$yt.resize(w,h,mw,mh);expect(sizes.length).to.equal(2);expect(sizes[0]).to.equal(50);expect(sizes[1]).to.equal(50);}]));
// $appStorage Factory 검증 it('should store and save data properly',inject(['$appStorage',function($storage){varkey='key',value='value';$storage.enableCaching();$storage.put(key,value);expect($storage.isPresent(key)).to.equal(true);expect($storage.get(key)).to.equal(value);$storage.erase(key);expect($storage.isPresent(key)).to.equal(false);$storage.put(key,value);$storage.flush();expect($storage.isPresent(key)).to.equal(false);}]));
// 화면 변경이 있을 경우 onChange() 호출
before(function(){test.$rootScope.$on('$routeChangeSuccess',function(){if(onChange)onChange();});});before(function(){$injector=test.injector();});beforeEach(function(){//test.reset();});
// $appYoutubeSearcher 를 $injector로 부터 가져옴. findeVideo 호출함
it('should perform a JSONP operation to youtube and fetch data',function(done){var$yt=$injector.get('$appYoutubeSearcher');expect($yt).not.to.equal(null);//this is the first video ever uploaded on youtube//so I doubt it will be removed anytime soon//and should be a good testing itemvaryoutubeID='jNQXAC9IVRw';$yt.findVideo(youtubeID,false,function(q,data){expect(data).not.to.equal(null);expect(data.id).to.equal(youtubeID);done();});});
});
5. Testing Directives
- AngularJS입장에서 HTML 코드안에서 컴포넌트 역할이다
HTML상에 반복되며 격리될 수 있는 것을 Directive로 만든다. 따라서 HTML 코드 내역은 최소화 되고 불필요한 반복을 줄일 수 있다.
- Directive가 $scope와 DOM을 가지고 어떻게 작동할지 테스트 해야 한다
- XHR을 호출할 수도 있다. 따라서 Unit, Midway, E2E 전부분의 테스트를 수행한다
// 화면을 이동하였을 때 Directive가 표현하는 HTML 내역이 있는지 검증it('should have a working welcome directive apply it\'s logic to the page',function(){browser().navigateTo('#/videos');expect(browser().location().path()).toBe("/videos");expect(element('#app-welcome-text').html()).toContain('Welcome');});it('should have a working youtube listing directive that goes to the right page when clicked',function(){browser().navigateTo('#/videos');element('.app-youtube-listing').click();expect(browser().location().path()).toMatch(/\/videos\/.+/);});});
6. Testing Templates, Partials & Views
- 격리된 HTML 코드이다
- 앵귤러에서는 ngView 또는 ngInlcude 통하여 렌더링된다
- 뷰는 $templateCache 서비스 통해서 캐싱되어 제공된다.
테스트시 templateUrl을 주면 캐싱된 mock template을 얻어와 사용할 수 있다
// templateUrl을 test.path('XXX') 호출후 template 명칭이 맞는지 검증it("should load the template for the videos page properly",function(done){onChange=function(){setTimeout(function(){varcurrent=test.route().current;varcontroller=current.controller;vartemplate=current.templateUrl;expect(template).to.equal('templates/views/videos/index_tpl.html');onChange=nulldone();},1000);};test.path('/videos?123');});
// 각 templateUrl 이동시 화면 내역을 검증it('should redirect and setup the videos page template on root',function(){browser().navigateTo('#/');expect(element('#ng-view').html()).toContain('youtube_listing');});it('should load the watched videos template into view',function(){browser().navigateTo('#/watched-videos');expect(element('#ng-view').html()).toContain('youtube_listing');});it('should load the watched video template into view',function(){browser().navigateTo('#/videos/123');expect(element('#ng-view').html()).toContain('profile');});it('should redirect back to the index page if anything fails',function(){browser().navigateTo('#/something/else');expect(element('#ng-view').html()).toContain('youtube_listing');});
});
7. Testing Filters
- filter 테스트시에는 $injector 서비스가 필요하고, $injector로 부터 $filter를 얻어온다
- Unit Testing
// test/unit/filters/filtersSpec.js//describe("Unit: Testing Filters",function(){beforeEach(angular.mock.module('App'));it('should have a range filter',inject(function($filter){expect($filter('range')).not.to.equal(null);}));
// $filter 서비스를 받아서 range 필터를 얻는다
it('should have a range filter that produces an array of numbers',inject(function($filter){varrange=$filter('range')([],5);expect(range.length).to.equal(5);expect(range[0]).to.equal(0);expect(range[1]).to.equal(1);expect(range[2]).to.equal(2);expect(range[3]).to.equal(3);expect(range[4]).to.equal(4);}));it('should return null when nothing is set',inject(function($filter){varrange=$filter('range')();expect(range).to.equal(null);}));
// range filter의 input에 대한 검증it('should return the input when no number is set',inject(function($filter){varrange,input=[1];range=$filter('range')(input);expect(range).to.equal(input);range=$filter('range')(input,0);expect(range).to.equal(input);range=$filter('range')(input,-1);expect(range).to.equal(input);range=$filter('range')(input,'Abc');expect(range).to.equal(input);}));
// 최초 한번 미리 $filter 서비스를 얻어온다 before(function(done){ngMidwayTester.register('App',function(instance){test=instance;test.$rootScope.$on('$routeChangeSuccess',function(){if(onChange)onChange();});$filter=test.filter();done();});});beforeEach(function(){test.reset();});it('should have a working range filter',function(){expect($filter('range')).not.to.equal(null);});
// html 템플릿 DOM을 만들어서 append 하고, Directives에 사용한 range에 대해서 검증it('should have a working filter that updates the DOM',function(done){varid='temp-filter-test-element';varhtml='<div id="'+id+'"><div class="repeated" ng-repeat="n in [] | range:10">...</div></div>';varelement=angular.element(html);angular.element(document.body).append(html);test.directive(element,test.scope(),function(element){varelm=element[0];setTimeout(function(){varkids=elm.getElementsByTagName('div');expect(kids.length).to.equal(10);done();},1000);});});});
// range 필터가 사용된 화면으로 이동하였을 경우 화면이 정상적으로 표시 되는지 검증
it('should have a filter that expands the stars properly',function(){browser().navigateTo('#/videos/WuiHuZq_cg4');expect(repeater('#app-youtube-stars > .app-youtube-star').count()).toBeGreaterThan(0);});});
소셜네트워크가 IT와 만나고 다시 스마트폰을 만나면서 새로운 방향을 제시하였다. 이런 방향을 지원하기 위하여 많은 OSS(Open Source Software)가 나왔고, HTTP 통신(Stateless)을 통하여 유입되는 어마어마한 사용자를 수용하기(규모의 확장) 위하여 사람들의 생각을 저장하고 활용해야 하는 필요성이 커졌다. 사람의 생각을 Key=Value로 많은 데이터를(BigData) 저장할 수 있어야 한다. 이러한 사상은 예전의 Client-Server 사상이 아닌 HTTP Web상의 통합과 유입을 처리하는 SNS(Social Network Service), SND(Social Network Data), SNG(Social Network Game) 사상으로 가고 있다. SNS는 HTTP 프로토콜을 통하여 Big Data Stream을 만들어 Real Time으로 반응하는 것이다.
기술의 흐름에 따른 선택
1) 전통적 웹기술
- 전통 pc application 개발 기술
- static 기술
- dynamic 기술
3) 모바일 기술
- embedded 기술 (고립된 기술) : iphone, android 등 mobile application 개발 기술 또는 Sencha 기술
- open api 기술 : web services 기술
4) 웹앱(WebApp) 기술 : 3세대 웹
- SPA(스타) : single page application 기술 (javascript, json)
의사결정의 반대는 지지부진 의사결정을 미루는 것이다. 우유부단함(indecision)은 다른 사람들이 당신을 대신해 의사결정내릴 때까지 망설이게 만든다. 그러므로, 중요한 의사결정은 마감일(deadline)이 있어야 한다. 당신이 "자율적으로(will)"으로 결정을 할 수 있는 최종기한을 뜻한다.
2. Discover the knowns.
최선의 판단을 하기 위해선, 의사결정에 필요한 정보들을 획득해야한다. 그런데, 정보를 취합할 때, 수확체감(diminishing returns)의 법칙이 적용된다는 것을 상기하라. 너무 세세한 디테일들에 과도하게 사로잡히기 시작할 때, 정보 수집을 멈추는 것이 좋다.
3. Gather relevant inputs.
의사 결정 이후에, 다른 사람들이 그 결정된 내용을 실행해야 한다면, "반드시" 그 사람들의 관점을 의사결정 내리기 전에 반영하라. 이 과정을 빠뜨리게 되면, 그 사람들은 당신이 내린 의사결정을 자신들의 것으로 수용(own)하지 않을 것이다. 당연히 매끄럽게 실행되기도 힘들다.
4. Decide.
결정하다(decide)의 어원은 라틴 말로 절단(cut off)에서 나왔다. 결정은 논쟁을 중단시키는 것이며, 다른 결정을 내릴 가능성들을 쳐내는 것이다. 일단 결정을 내리면, 바로 실행으로 나아가라. 그렇지 않으면, 당신이 내린건 진짜 의사결정이 아니다.
5. Explain your reasoning.
(3단계에서) 다른 사람들이 자신들의 관점을 피력할 기회를 주었다면, 이제 그들에게 왜 다른 옵션을 취하지 않고 이러한 의사결정을 내렸는지 설명하라. 그들은 기꺼이 당신의 의사결정에 따를 준비가 되어 있을 것이다.
6. Never second guess.
일단 의사결정을 내린 뒤에는 그 결정에 의구심을 갖지도 말고, 회의적인 시각을 지닌 사람들에게 심각하게 귀 기울이지 마라. 실제 실행에 온 힘을 기울이고, 결과를 얻기 전까지는 말이다. 이미 내려진 판단에 의심을 가질수록 우유부단함에 물들게 되며, 자연히 실행력도 떨어지게 된다.
7. Observe the results
정말로 진실되게 그리고 갖은 힘을 다해 실행에 주력했다면, 이제 한 걸음 뒤로 물러나 그 결과를 엄정하게 살펴보라. 기대하는 성과를 거두었다면 축하하라. 만약 그렇지 않다면, 8단계로 넘어가라.
8. Adjust the decision
명심하라!!! 꽉 막힌 완고함(Bullheadedness)은 우유부단함 못지 않게 당신을 대책없는 사람으로 전락시킨다. 오히려, 더 많은 자원과 노력을 낭비하게 만들면서 말이다. 그러므로, 기대하는 결과를 얻지 못했다면, 1단계로 다시 돌아가라. 그간 실행하면서 터득한 경험을 활용해 더 다듬고, 더 나은 의사결정을 내려라.