블로그 이미지
윤영식
Frontend Application Architecter, Full Stacker, KnowHow Dispenser and Bike Rider

Publication

Statistics Graph

Recent Comment

2014.06.02 10:09 AngularJS/Start MEAN Stack

프로트앤드 개발을 하면서 테스트는 어떻게 할 수 있는지 알아본다. 앵귤러에서는 테스트 관련 내용까지도 포함하고 있어서 개발 테스트의 편의성을 함께 제공하고 있다. 


사전 준비

  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);


  });


});

  


<참조>

  - Jasmine 문법

  - Jasmine 소개

  - Karma 테스트 하기

  - Protractor Seed 소개 : Protractor WebDriver 업데이트 

  - WebDriverJS 소개 

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
2013.10.23 17:48 Testing, TDD/Test First

AngularJS를 위한 Test 수행 방법을 생각해 보고, 테스트 전략에 대해서 다른 개발자는 어떻게 하고 있는지 알아보자 


  



1. Angular Testing 에 대한 고찰

 1-1. 스토리 보드 만들기 

  - 프로젝트 진행이 Agile Scrum 이라면 Story를 먼저 도출한다 

  - Story가 도출되었다면 스토리안에 Task를 나눈다 그리고 Task 별로 Validation(검증) 및 Processing(처리)해야할 것을 명세한다

  - 명세는 스토리카드안에 간단히 적는다. 


 1-2. UX&UI 디자인

  - 스토리에 맞는 화면의 목업(Mockup or WireFrame)을 그린다. Balsamiq같은 목업툴을 이용한다 

  - 화면 목업이 스토리에 부합되는지 확인 되면 스토리 개발 우선순위를 결정한다

  - 결정된 화면의 HTML을 Bootstrap 또는 BootFlat을 이용하여 코드를 만든다. 저작툴이 Bootply를 이용해도 된다 


 1-3. TDD 수행하기 개발

  - AngularJS 기반의 개발시 다음과 같이 진행을 한다 (AngularWay 참조)

    + View  === AngularJS의 Directive가 포함된 HTML이다 

    + Model === $scope를 통하여 Controller에서 Two way binding을 자동 수행한다 

    + View와 Controller를 통하여 표현과 행위를 분리한다 (SoC)

    + AngularJS 애플리케이션 접근법

      Step 1) Model을 먼저 생각하고 Service를 만든다

      Step 2) Model이 표현될 View에 대해 생각해 보고 template을 만들고, 필요하면 자동 모델 바인딩되는 Directive도 만든다.

      Step 3) 각 View 를 Controller에 연결한다 (ng-view and routing, ng-controller)


  - E2E Testing

    + AngularJS의 Config의 Routing 에 대해 E2E Test 수행 

    + Model을 표현하는 template에 대해 E2E Test 수행 

    + Directives를 HTML에 코딩하였다면 E2E Test 수행 

    + Filter가 HTML에 표현되는 것이라면 E2E Test 수행

    (Protractor 또는 Angular Test Scenario Runner 사용)

  - Unit Testing

    + Model을 서버 또는 Controller로 부터 받으면 Service/Factory에 대하여 Validation과 XHR등 스팩에 대한 Unit Test 수행

    + Model을 가공 핸들링하여 View와 양방향 연동하는 Controller에 대한 Unit Test 수행

    + Directives/Filter 의 내부 로직에 대한 Unit Test 수행 

    (Mocha 기반의 Chai와 같은 expectation, assertion 사용)


 1-4. 개발 툴체인

  - Yeoman 을 통하여 AngularJS 스케폴딩 코딩을 한다 (참조)

  - 1-3의 E2E 와 Unit Testing을 위하여 AngularJS의 Karma Test Framework안에서 사용하면 된다. 단, Protractor사용시 예외



2. 테스트 전략예 동영상 

  - AngularJS에 대한 Test 전략에 대한 동영상을 보자 

    + testem을 통해 Unit Test를 수행

    + protractor를 통해 E2E Test를 수행

    + 제품 통합 테스트 수행을 위하여 Yeoman과 유사한 Lineman을 사용

    + Jasmine-given을 사용하여 Given-When-Then의 CoffeeScript 방식의 재미있는 테스팅도 소개함

     


  - 새롭게 나온 E2E Test Framework인 Protractor for angularJS 

    + Angular팀에서 새롭게 소개된 Protractor 느낌상 Karma E2E 보다는 더 간단하고 직관적인듯 하다.

     



<참조>

  - End-to-End Testing with AngularJS

  - Front javascript testing framework

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
2013.10.21 19:01 Testing, TDD/Test First

Karma 테스트 프레임워크를 통하여 AngularJS기반 애플리케이션을 어떻게 테스트 하는지 알아보자 



AngularJS의 두가지 테스트 타입 

  - Unit Testing : 작고 격리된 코드 유닛의 동작에 대한 검증 테스트 

  - E2E Testing : 애플리케이션 전영역에 대한 테스트. E2E 테스터는 로봇처럼 브라우져를 실행하여 페이지를 검증해 준다 

  - AngularJS에서 이야기하는 E2E테스트 시나리오 API : Angular Scenario Runner 

    + describe 

    + beforeEach

    + afterEach

    + it

describe('Buzz Client', function() {

 it('should filter results', function() {

   input('user').enter('jacksparrow');

   element(':button').click();

   expect(repeater('ul li').count()).toEqual(10);

   input('filterText').enter('Bees');

   expect(repeater('ul li').count()).toEqual(1);

 });

});


  - Testing의 유형을 나누어 본다 : Unit, Midway, E2E

    + Unit Testing

      코드레벨 테스트

      격리된 테스트

      Mock & Stubbing 요구, 빠름

    + Midway Testing

      애플리케이션/코드레벨 테스트

      애플리케이션의 모든 부분 접근 가능

      다른 애플리케이션 코드와 상호작용

      뷰(html) 내부의 테스트는 불가능능

      빠름, 단, XHR일 경우 느릴 수 있음 

    + E2E Testing

      웹레벨 테스트

      웹서버가 필요

      완벽한 통합 테스트 



샘플코드 설치 및 수행

  - 데모 페이지

  - 소스 : GitHub Repository

  - 설치 (참조)

$ git clone git://github.com/yearofmoo-articles/AngularJS-Testing-Article.git

Cloning into 'AngularJS-Testing-Article'...

remote: Counting objects: 536, done.

remote: Compressing objects: 100% (368/368), done.

remote: Total 536 (delta 130), reused 516 (delta 117)

Receiving objects: 100% (536/536), 1.28 MiB | 390.00 KiB/s, done.

Resolving deltas: 100% (130/130), done.


$ cd AngularJS-Testing-Article/ && npm install


// 만일 제대로 npm install시 에러가 발생하면 package.json의 버전을 바꾼다

$ vi package.json

   "grunt-karma": "latest",

    "karma": "latest",

  - 수행 : http://localhost:8000 으로 호출한다 

$ grunt server

Running "connect:server" (connect) task

Starting connect web server on localhost:8000.

Waiting forever...

  - 디렉토리 구조 

    + test 밑에 3가지의 karma 환경파일이 존재한다

    + grunt test 명령으로 test 프로그램을 수행할 수 있다 - grunt test, grunt karma:unit, grunt karma:midway, grunt test:e2e (참조)

       * 테스트 안될 경우 주의

        1) bower_components를 components 폴더로 명칭 변경

        2) ngMidwayTester 못 찾으면 : comoponents/ngMidwayTest/src 에서 src를 Source 폴더로 명칭 변경  

    + 각 파트별 수행 테스트 파일은 unit, midway, e2e 폴더로 나뉜다 

    

  - Karma안에서 사용한 Test Frameworks은 test/ 폴더 밑으로 karma-e2e/midway/unit/shared.conf.js 로 설정되어 있다 

    + Mocha.js : Unit Test

    + Chai.js : matchers and assertions

    + Jasmine.js : End-To-End Test(E2E) Framework으로 Angular Scenario Runner에서 사용


  * Midway Plugin에서 에러가 발생한다. 해결하면 500원



Mocha, Chai 그리고 Angular Scenario Runner

  - 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() {

    var module;
    before(function() {
      module = angular.module("App");
    });

    it("should be registered", function() {
      expect(module).not.to.equal(null);
    });

    // App이 의존하는 모듈들이 있는지 검증 
    describe("Dependencies:", function() {

      var deps;
      var hasModule = function(m) {
        return deps.indexOf(m) >= 0;
      };
      before(function() {
        deps = module.value('appName').requires;
      });

      //you can also test the module's dependencies
      it("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);
      });
    });
  }); 

});



2. Testing Routes

  - 애플리케이션의 페이지 라우팅이 제대로 되는지 테스트 한다 

  - Midway, E2E 테스트에서 수행한다 

  - Midway Testing (Midway Testser Plugin)

//
// test/midway/routesSpec.js
//
describe("Testing Routes", function() {

  var test;

  before(function(done) {
    test = new ngMidwayTester();
    test.register('App', done);
  });

  it("should have a videos_path", function() {
    expect(ROUTER.routeDefined('videos_path')).to.equal(true);
    var url = ROUTER.routePath('videos_path');
    expect(url).to.equal('/videos');
  });

  it("the videos_path should goto the VideosCtrl controller", function() {
    var route = 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);
    var url1 = ROUTER.routePath('home_path');
    var url2 = 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) {
    var searchTestAtr = 'cars';
    var response = $httpBackend.expectJSONP('https://gdata.youtube.com/feeds/api/videos?q='
                     + searchTestAtr + '&v=2&alt=json&callback=JSON_CALLBACK');
    response.respond(null);

    var $scope = $rootScope.$new();
    var ctrl = $controller('VideosCtrl', {
      $scope : $scope,
      $routeParams : {
        q : searchTestAtr
      }
    });
  }));

  it('should have a properly working VideoCtrl controller', 
       inject(function($rootScope, $controller, $httpBackend) {
    var searchID = 'cars';
    var response = $httpBackend.expectJSONP('https://gdata.youtube.com/feeds/api/videos/' 
                     + searchID + '?v=2&alt=json&callback=JSON_CALLBACK');
    response.respond(null);

    var $scope = $rootScope.$new();
    var ctrl = $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() { };
    var ctrl = $controller('WatchedVideosCtrl', {
      $scope : $scope
    });
  }));
 

});

  - Midway Testing

// test/midway/controllers/controllersSpec.js

describe("Midway: Testing Controllers", function() {

  var test, onChange;
  // Async Test로 done 객체 파라미터 전달 
  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      done();
    });
  });

  // 라투트통하여 페이지 변경 성공 이벤트 발생하면 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');
      var current = test.route().current;
      var controller = current.controller;
      var scope = 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');
      var current = test.route().current;
      var controller = current.controller;
      var params = current.params;
      var scope = current.scope;

      expect(controller).to.equal('WatchedVideosCtrl');
      done();
    };
    test.path('/watched-videos');
  });

});

  - E2E Testing

// test/e2e/controllers/controllersSpec.js
//
describe("E2E: Testing Controllers", function() {

  // 첫 페이지 있는지 검증 
  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) {
    var w = 100;
    var h = 100;
    var mw = 50;
    var mh = 50;
    var sizes = $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) {
    var key = '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);
  }]));
 

});

  - Midway Testing 

// test/midway/services/servicesSpec.js
//
describe("Midway: Testing Services", function() {

  var test, onChange, $injector;

  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      done();
    });
  });

  // 화면 변경이 있을 경우 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 item
    var youtubeID = '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 전부분의 테스트를 수행한다 

  - Unit Testing 

// test/unit/directives/directivesSpec.js
//
describe("Unit: Testing Directives", function() {

  var $compile, $rootScope;

  beforeEach(angular.mock.module('App'));

  // 
  beforeEach(inject(
    ['$compile','$rootScope', function($c, $r) {
      $compile = $c;
      $rootScope = $r;
    }]
  ));

  // app-welcome Directive안에 Welcome 글자와 매치하는지 검증
  it("should display the welcome text properly", function() {
    var element = $compile('<div data-app-welcome>User</div>')($rootScope);
    expect(element.html()).to.match(/Welcome/i);
  })

});

  - Midway Testing

// test/midway/directives/directivesSpec.js
//
describe("Midway: Testing Directives", function() {

  var test, $injector;

  // $injector 얻어오기 
  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      done();
    });
  });

  before(function() {
    $injector = test.$injector;
  });

  // $appYoutubeSearcher 통하여 응답을 받은후 app-youtube-listings Directive를 검증한다. XHR호출이므로 setTimeout 값으로 1초를 준다 
  it("should properly create the youtube listings with the directive in mind", function(done) {
    var $youtube = $injector.get('$appYoutubeSearcher');

    var html = '';
    html += '<div data-app-youtube-listings id="app-youtube-listings">';
    html += ' <div data-ng-include="\'templates/partials/youtube_listing_tpl.html\'" data-ng-repeat="video in videos"></div>';
    html += '</div>';

    var $scope = test.scope();
    var element = angular.element(html);

    $youtube.query('latest', false, function(q, videos) {
      $scope.videos = videos;

      test.directive(element, $scope, function(element) {
        setTimeout(function() {
          var klass = (element.attr('class') || '').toString();
          var hasClass = /app-youtube-listings/.exec(klass);
          expect(hasClass.length).to.equal(1);

          var kids = element.children('.app-youtube-listing');
          expect(kids.length > 0).to.equal(true);
          done();
        },1000);
      });
    });
  });

});

  - E2E Testing

// test/e2e/directives/directivesSpec.js
//
describe("E2E: Testing Directives", function() {

  beforeEach(function() {
    browser().navigateTo('/');
  });

  // 화면을 이동하였을 때 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을 얻어와 사용할 수 있다 

  - Midway Testing

// test/midway/templates/templatesSpec.js
//
describe("Midway: Testing Requests", function() {

  var test, onChange;
  var $location, $injector, $params;

  // url path 변경시 onChange() 호출
  before(function(done) {
    ngMidwayTester.register('App', function(instance) {
      test = instance;
      test.$rootScope.$on('$routeChangeSuccess', function() {
        if(onChange) onChange(); 
      });
      done();
    });
  });

  before(function() {
    $injector = test.$injector;
    $location = test.$location;
    $params   = test.$routeParams;
  });

  beforeEach(function() {
    onChange = null;
  });

  // templateUrl을 test.path('XXX') 호출후 template 명칭이 맞는지 검증
  it("should load the template for the videos page properly", function(done) {
    onChange = function() {
      setTimeout(function() {
        var current = test.route().current;
        var controller = current.controller;
        var template = current.templateUrl;
        expect(template).to.equal('templates/views/videos/index_tpl.html');
        onChange = null
        done();
      },1000);
    };
    test.path('/videos?123');
  });
 

});

  - E2E Testing

// test/e2e/templates/templatesSpec.js
//
describe("E2E: Testing Templates", function() {

  beforeEach(function() {
    browser().navigateTo('/');
  });

  // 각 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) {
    var range = $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) {
    var range = $filter('range')();
    expect(range).to.equal(null);
  }));

  // range filter의 input에 대한 검증
  it('should return the input when no number is set', inject(function($filter) {
    var range, 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);
  }));
 

});

  - Mideway Testing

// test/midway/filters/filtersSpec.js

//
describe("Midway: Testing Filters", function() {

  var test, onChange, $filter;

  // 최초 한번 미리 $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) {
    var id = 'temp-filter-test-element';
    var html = '<div id="' + id + '"><div class="repeated" ng-repeat="n in [] | range:10">...</div></div>';
    var element = angular.element(html);
    angular.element(document.body).append(html);

    test.directive(element, test.scope(), function(element) {
      var elm = element[0];
      setTimeout(function() {
        var kids = elm.getElementsByTagName('div');
        expect(kids.length).to.equal(10);
        done();
      },1000);
    });
  });

});

  - E2E Testing 

// test/e2e/filters/filtersSpec.js

//
describe("E2E: Testing Filters", function() {

  beforeEach(function() {
    browser().navigateTo('/');
  });

  // 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);
  });

});



<참조>

  - 원문 : Full Spectrum Test in AngularJS 

  - Javascript 테스트를 어렵게 하는 6가지

  - Service Test 전략 동영상

    


  - AngularJS를 위한 새로운 E2E Test Framework "Protractor"

  - Protractor로 E2E 테스트하기 : 기존 Karma E2E 보다 쉬워진 느낌이다. 30분 부터 보시라

    


저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
2013.10.20 21:12 Testing, TDD/Test First

AngularJS 기반하에 TDD를 수행하거나 테스트 코드를 작성할 때 적합한 툴과 개념에 대해 이해를 하자



1. Unit Testing

  - 단위 기능 테스트 

  - 추천 단위 테스트 툴

    + Mocha : 단순하며 유연한 테스트 프레임워크 (추천)

    + Jasmine : Behavior-Driven Development Framework for testing Javascript (추천)

    + QUnit : jQuery에서 사용

  - 단위 테스트 만들기 

    + 사용자 스토리 기반으로 만들어 보자 

       "사용자로써 나의 앱에 로그인을 하면 대시보드 페이지를 본다"

    + Story -> Features -> Units 로 구분을 한다 

       "로그인 폼"을 하나의 기능으로 선택한다

       하나의 스토리는 여러 기능의 합집합이며, 각 기능의 정상작동 검증(Verification)을 거쳐서 하나의 스토리는 완성된다 

    + 로그인 폼에 대한 Spec을 만든다 (BDD)

describe('user login form', function() {


    // critical : 기능의 검증

    it('ensure invalid email addresses are caught', function() {});

    it('ensure valid email addresses pass validation', function() {});

    it('ensure submitting form changes path', function() { });


    // nice-to-haves

    it('ensure client-side helper shown for empty fields', function() { });

    it('ensure hitting enter on password field submits form', function() { });


});

   

  - 위의 테스트 Spec을 AngularJS로 단위 테스트 만들기 

    + 파일 : angular.js, angular-mocks.js, app.js(모듈명-App), loginController.js(LoginCtrl),  loginService.js(LoginService)

    + beforeEach에서 angular.mock.module 통해 모듈을 셋업한다 

       beforeEach에서 angular.mock.inject 통해 controller에서 $scope 셋업한다 (참조) 

    + it 에서 inject 통하여 service를 셋업한다 (참조)

describe("Unit Testing Examples", function() {


  beforeEach(angular.mock.module('App')); 


  it('should have a LoginCtrl controller', function() {

    expect(App.LoginCtrl).toBeDefined();

  });


  it('should have a working LoginService service', inject(['LoginService',

    function(LoginService) {

      expect(LoginService.isValidEmail).not.to.equal(null);


      // test cases - testing for success

      // 검증을 위한 테스트 데이터 

      var validEmails = [

        'test@test.com',

        'test@test.co.uk',

        'test734ltylytkliytkryety9ef@jb-fe.com'

      ];


      // test cases - testing for failure

      var invalidEmails = [

        'test@testcom',

        'test@ test.co.uk',

        'ghgf@fe.com.co.',

        'tes@t@test.com',

        ''

      ];


      // you can loop through arrays of test cases like this

      for (var i in validEmails) {

        var valid = LoginService.isValidEmail(validEmails[i]);

        expect(valid).toBeTruthy();

      }

      for (var i in invalidEmails) {

        var valid = LoginService.isValidEmail(invalidEmails[i]);

        expect(valid).toBeFalsy();

      }


    }]) // end inject 

  ); // end it 

}); // end describe



2. Integration Testing 

  - 통합 테스트는 end-to-end 테스트(E2E)이고, 수행순서대로 차례로 수행해 보는 것이다. 

     그리고 애플리케이션과 UI의 상태를 검증한다. 심지어 Visual적인 부분에 대한 검증도 가능하다 

  - 추천 도구 

    + Karma : AngularJS에서 공식사용 (추천)

    + CasperJS : headless browser로 사용 

    + PhantomJS : headles brower

  - 통합 테스트 만들기 

    + "사용자로써 나의 앱에 로그인하여 대시보드 페이지를 본다"

describe('user login', function() {


  // 수행 및 결과 상태의 검증

  it('ensures user can log in', function() {

    // expect current scope to contain username

  });

  it('ensures path has changed', function() {

    // expect path to equal '/dashboard'

  });


});


  - 위의 테스트 Spec을 AngularJS로 통합 테스트 만들기 (참조)

    + 최근에는 Angular Scenario Test Runner와 같은 Protractor E2E test framework이 새롭게 소개되고 있다 

describe("Integration/E2E Testing", function() {


  // start at root before every test is run

  beforeEach(function() {

    browser().navigateTo('/');

  });


  // test default route

  it('should jump to the /home path when / is accessed', function() {

   // 루트 패스로 이동 - 로그인 화면 

    browser().navigateTo('#/');

    expect(browser().location().path()).toBe("/login");

  });


  it('ensures user can log in', function() {

    // 로그인 화면인지 체크

    browser().navigateTo('#/login');

    expect(browser().location().path()).toBe("/login");


    // assuming inputs have ng-model specified, and this conbination will successfully login

    // 테스트 데이터를 통해 브라우져 로그인 submit 버튼 click 이벤트 발생시킴 

    input('email').enter('test@test.com');

    input('password').enter('password');

    element('submit').click();


    // logged in route

    // 로그인이 성공했을 경우 대시보드 패스에 있는지 검증

    expect(browser().location().path()).toBe("/dashboard");


    // my dashboard page has a label for the email address of the logged in user

    // DIV의 id="email"의 html 값이 로그인시 입력한 값인지 검증 

    expect(element('#email').html()).toContain('test@test.com');

  });


  it('should keep invalid logins on this page', function() {

    // 로그인 화면 인지 검증

    browser().navigateTo('#/login');

    expect(browser().location().path()).toBe("/login");


    // assuming inputs have ng-model specified, and this conbination will successfully login

    // 잘 못된 값을 넣었을 가지고 로그인 버튼 클릭 

    input('email').enter('invalid@test.com');

    input('password').enter('wrong password');

    element('submit').click();


    // DIV id="message"에 failed메세지 있는지 검증 

    expect(element('#message').html().toLowerCase()).toContain('failed');


    // logged out route

    // 제대로 로그인을 못했으므로 로그인 패스 그대로인지 검증 

    expect(browser().location().path()).toBe("/login");

  }); // end it 

}); // end describe 



3. Mocking Services and Modules in AngularJS

  - 목(Mock) 서비스를 이용한 테스트 방법 

  - 목 오브젝트를 사용하는 이유는 외부적인 요소 의존적인 부분을 제거하고 테스트 하기 위해서 이다 

  - ngMock.$httpBackend : service에서 http login 호출을 하면 응답 JSON 객체를 넘겨주는 mock 객체를 만든다 

it('should get login success',

  inject(function(LoginService, $httpBackend) {


    $httpBackend.expect('POST', 'https://api.mydomain.com/login')

      .respond(200, "[{ success : 'true', id : 123 }]");


    LoginService.login('test@test.com', 'password')

      .then(function(data) {

        expect(data.success).toBeTruthy();

    });


  $httpBackend.flush();

});



<참조>

  - 원문 : Best Practices in AngularJS

  - AngularJS Controller Unit Test 방법

  - AngularJS Service Unit Test 방법

  - Full Spectrum Testing(E2E) in Angular with Karma (필독)

  - Introducing E2E Testing in Angular 동영상

    

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
2013.04.15 11:44 Dev Environment/Build System

AngularJS 개발시 반복적인 작업을 제거하고 생산성을 높일 수 있는 통합 도구인 Yeoman의 사용법에 대해 알아보고, 처음 개발을 시작할 때 코드의 틀을 잡아 주는 몇가지 Seed 프로젝트를 통하여 어떤 구조로 애플리케이션을 만들어야 하는지 알아두자



1. 필요 요소 기술

  - UI : Bootstrap from Twitter  간혹 Foundation을 이용

  - Build/Preview : Grunt

  - Test : 기본은 Karma이고, Mocha를 사용하고 싶다면 Karma-Mocha Karma구동을 위한 Mocha adapter 존재

  - Package Management : Bower from Twitter

이들의 통합적으로 사용하기 위한 툴로 yeoman 을 설치하면 된다. 간단한 명령으로 AngularJS 관련 코드를 자동 생성하여 준다



2. Yeoman startup

  

yo - the scaffolding tool from Yeoman

bower - the package management tool  

  + component.json : 의존관계 설정

  + .bowerrc : 컴포넌트 설치 위치정보

grunt - the build tool 

  + Gruntfile.js : ant와 같은 수행 Task 정의

  + package.json : Node.js위에 구동되므로 package.json에 Grunt contributor 라이브러리 정의

karma - grunt test 시에 수행 

  + karma.conf.js : 로컬 테스트 환경

  + karma-e2e.conf.js : Real Browser 테스트 환경 즉, end-to-end (Client<->Server) 테스트 수행. 사용 port 8080


  - Yeoman command for AngularJS

// 설치 : grunt-cli, bower가 같이 설치된다 

npm install -g yo


// Yeoman을 위한 generator 설치 for AngularJS & Karam

// 하기 에러 내역 참조하여 generator-angular의 script-base.js 파일 수정

// Generator는 Yeoman에서 생성하는 boilerplate 나 scaffolding 코드를 만들어주는

// Yeoman의 플러그인이라 생각하면 된다. 이미 만들어지 Generator 목록이 존재한다

// > https://github.com/yeoman (generator-* 목록들)

// > Generator 만들기 참조

npm install -g generator-webapp generator-angular generator-karma


// 명령어 (프로젝트 디렉토리에서 수행)

yo angular 또는 yo angular:app 애플리케이션 생성

yo angular:controller myControllerName 컨트롤러 생성

yo angular:directive myDirectiveName 디렉티브 생성

yo angular:filter myFilterName 필터 생성

yo angular:service myServiceName 서비스 생성


  - yo angular:* 사용시 에러 발생 해결방법

예) yo angular:controller Test 수행시 하기와 같은 에러 발생

~/angularjs/sd_01> yo angular:controller Test


path.js:360

        throw new TypeError('Arguments to path.join must be strings');

              ^

TypeError: Arguments to path.join must be strings

    at path.js:360:15

    at Array.filter (native)

    at Object.exports.join (path.js:358:36)

    at Generator (/usr/local/lib/node_modules/generator-angular/script-base.js:38:29)

    at new Generator (/usr/local/lib/node_modules/generator-angular/controller/index.js:10:14)


==> 해결하기 

1) /usr/local/lib/node_modules/generator-angular/script-base.js 의 38 번째 줄 내용을 하기와 같이 수정함

 37     if (!this.options.coffee &&

 38       //this.expandFiles(path.join(this.appPath, '/scripts/**/*.coffee'), {}).length > 0) {  // 주석처리 

 39       this.expandFiles(path.join(process.cwd(), '/scripts/**/*.coffee'), {}).length > 0) {  // process.cwd() 로 바꿈

 40       this.options.coffee = true;

 41     }


2) 재수행 : 친절하게도 TestCase까지 자동 생성해 준다 

   ~/angularjs/sd_01> yo angular:controller Test

   create app/scripts/controllers/Test.js

   create test/spec/controllers/Test.js


  - Yeoman command for Bower : 의존관계 라이브러리에 대한 관리 담당 

// 명령어

bower search <dep> - search for a dependency in the Bower registry

bower install <dep>..<depN> - install one or more dependencies

bower list - list out the dependencies you have installed for a project

bower update <dep> - update a dependency to the latest version available


// 설치하기 

// .bowerrc 에 설정된 디렉토리로 다운로드된 라이브러리가 설치된다

$ bower install angular-ui

bower cloning git://github.com/angular-ui/angular-ui.git

bower cached git://github.com/angular-ui/angular-ui.git

bower fetching angular-ui

bower checking out angular-ui#v0.4.0

bower copying /Users/nulpulum/.bower/cache/angular-ui/bd4cf7fc7bfe2a2118a7705a22201834

bower installing angular-ui#0.4.0


$ cd app/components/

$ ls

angular angular-scenario es5-shim json3

angular-mocks angular-ui jquery


$ cd ../..

$ cat .bowerrc

{

    "directory": "app/components"

}


  - Yeoman command for Grunt : 프러덕션 빌드, 미리보기, 테스팅 

    + grunt는 task에 대한 여러 target들이 존재하는 구조이다. 

    + 명령을 grunt <task> 로 주면 task밑의 모든 target이 순서대로 수행된다

    + 명령을 grunt <task>:<target> 을 지정하여 특정 target만 수행할 수도 있다

    + task와 관련한 여러가지 Plugins 이 이미 만들어져 있고 직접 만들 수도 있다 (기존 Plugins 목록)

// 명령어

grunt test  - 테스트하기. run the unit tests for an app  

grunt server - 브라우져에서 보기. 변경사항 실시간 반영됨. preview an app you have generated (with Livereload)

grunt - 배포본 빌드하기.  build an optimized, production-ready version of your app  (warning 무시 옵션 --force 사용가능)


// 브라우져 미리보기 in app root folder

grunt server

// warning -jslint 같은- 무시하고 계속 build하기

grunt  --force 



3. 기본 코드 생성

  - Grunt-Express : Grunt와 Express 연동하기

  - Yeoman-Angular-Express : Yeoman을 이용하여 Angular project 생성한 후 Express와 통합하는 예제

  - Angular-Socket.io-Seed : Angular와 Socket.io를 접목 시키고자 할때 사용 (포스팅)



4. Addy Osmani 님이 이야기 하는 Automation Front-end workflow



결론적으로 Yeoman을 사용하여 기반 코드를 생성하고 빌드/테스팅 자동화를 한다. 그리고 필요한 코드들은 Seed 프로젝트를 참조하여 자기것으로 만들면 되겠다



<참조>

  - 원문 : Google, Twitter and AngularJS

  - 보다 많은 AngularJS와 BackboneJS에 대한 기사 in DailyJS

  - Grunt를 위한 express 플러그인 테스트

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식

AngularJS의 테스트툴로 Testacular를 사용하는데 명칭이 Karma로 바뀌었다. WebStorm에 설정하고 사용하는 방법을 알아보자 



1) Karma 설치하기

  - 홈페이지 : http://karma-runner.github.io/0.8/index.html

  - 선조건 : WebStorm 설치 및 Node.js 설치

  - Karma를 global로 설치 : npm install -g karma



2) Yeoman을 통한 Karma 환경설정 

  - 일반적으로 Yeoman을 통하여 스케폴딩 코드를 만들어 사용하는 것이 좋다 : Yeoman 홈페이지

  - yeoman 설치 : npm install -g yo grunt-cli bower

  - angular 프로젝트 만들기 : yo angular 또는 yo angular:app

    

    <자동 생성된 파일 및 디렉토리 구조>


  - 자동 생성된 karma 테스트 환경파일 : karma.conf.js

/ Karma configuration
 
// base path, that will be used to resolve files and exclude
basePath = '';
 
// list of files / patterns to load in the browser
files = [
JASMINE,
JASMINE_ADAPTER,
'app/components/angular/angular.js',
'app/components/angular-mocks/angular-mocks.js',
'app/scripts/*.js',
'app/scripts/**/*.js',
'test/mock/**/*.js',
'test/spec/**/*.js'
];
 
// list of files to exclude
exclude = [];
 
// test results reporter to use
// possible values: dots || progress || growl
reporters = ['progress'];
 
// web server port
port = 8080;
 
// cli runner port
runnerPort = 9100;
 
// enable / disable colors in the output (reporters and logs)
colors = true;
 
// level of logging
// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
logLevel = 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'];
 
// If browser does not capture in given timeout [ms], kill it
captureTimeout = 5000;
 
// Continuous Integration mode
// if true, it capture browsers, run tests and exit
singleRun = false;



3) WebStorm 환경설정

  - Server 설정하기 

    + 웹스톰 상단의 메뉴에서 Edit Configurations... 클릭

    

   + Node.js로 "Karma Server" 생성 : node.js, karma 설치 위치와 프로젝트 위치, 파라미터로 start 입력

      카르마의 테스트 서버 역할을 수행


    + Karma Run 생성 : 파라미터로 run 입력, 테스트를 수행한다 


    + Karma Debug : JavaScript Debug의 Remote로 생성 

       Remote URL 로 "http://localhost:8080/" 입력 



4) 테스트 수행하기 

  - Karma Server 시작하기 

    + Karma Server 선택하고 > 시작 버튼 클릭

    + 하단에 localhost:8080 으로 Listen하는 서버 구동됨 

    + 브라우져가 자동 실행 : 자동 실행안되면 브라우져 띄워서 8080 호출 



  - Karma Run 테스트 하기 

    + 테스트 수행하기 

    + 테스트가 정상 수행되면 SUCCESS가 나온다. 비정상이면 FAILED 메세지


  - Karma Debug

    + 소스 코드에 Break point를 만들고 디버그 수행

    + 테스트 해본 결과 잘 안되는데 향후 다시 한번 체크해 봐야 겠다



5) 테스트 코드 

  - BDD 방식의 테스트 코드를 짠다 

//////////////

// 업무 코드 

'use strict';
 
var app = angular.module('eggheadioApp');
 
app.controller('MainCtrl', function ($scope) {
$scope.awesomeThings = [
'HTML5 Boilerplate',
'AngularJS',
'Karma'
];
}) ;
 
app.factory('Data', function() {
return {message: 'shared data'}
});
 
app.filter('reverse', function(Data) {
return function(text) {
return text.split("").reverse().join("");
}
});


//////////////////////////

// 필터에 대한 테스트 코드 

describe('filter', function() {
beforeEach(module('eggheadioApp'));
 
describe('reverse', function() {
it('should reverse a string', inject(function(reverseFilter){ //reverse라는 Filte 파라미터
expect(reverseFilter('ABCD')).toEqual('DCBA');
}))
})
})



<참조>

  - 원본 : 유튜브 동영상

저작자 표시 비영리 변경 금지
신고
posted by peter yun 윤영식
prev 1 next