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

Publication

Category

Recent Post

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 윤영식
2013. 4. 3. 14:15 Testing, TDD/Test First

Node.js를 사용하기로 하면서 TDD를 위하여 Mocha를 선택하였다. 그리고 클라이언트단의 테스트 또한 Mocha로 이용해 보려 하였으나 DOM 조작이 많을 경우와 테스트 간결함등등에 강점이 많은 QUnit이 오히려 매력적으로 다가 왔다



1) QUnit vs Mocha

  - QUnit

    + DOM 조작관련 Client 단위 테스트에 적합

    + assert.equal이 아니라 equal만 사용하면 됨 

    + UI에서 테스트 모듈별로 콤보 선택하여 볼 수 있음

    + 클라이언트단 라이브러리들에서 주로 사용함 (BackboneJS 등)

    + PhantomJS와 연계하여 테스트

   



  - Mocha

    + 다양한 라이브러리를 접목 할 수 있다

    + 브라우져 UI 와 소스보기등이 QUnit 보다 나아 보임

    + equal 이 아니라 assert.equal을 씀 (assert의 경우) 즉, DRY 위배

    + UI에서 테스트 결과 전체가 리스팅 되고 선택하여 볼 수 없음 

 


** Backbone의 QUnit  코드를 Mocha를 가지고 메소드별로 TDD방식으로 테스트 해보았다. 라이브러리 테스트 코드를 하나하나 체크하면 전체 API의 동작방식을 심도 있게 이해 할 수 있다


https://github.com/ysyun/prototyping/tree/master/tdd/backbone-mocha/tests



2) 결론

  - 클라이언트 : QUnit + PhantomJS(CasperJS) 를 사용하여 테스트 진행

  - 서버 : Mocha + Assert 을 사용



<참조>

  - 원문 : Test  Libraries 비교 - 테스트 라이브러리별 사용처 권고함

posted by 윤영식
prev 1 next