Angular(二)Jasmineユニット試験とエンドツーエンド試験


ユニットテストは、開発者がコードの一部の有効性を検証するのに役立つ技術です.エンドツーエンドテスト(E 2 E)は、複数のコンポーネントが想定通りに統合されて動作することを確保したい場合に使用します.現代的なJavaScript MVWフレームワークとして、ユニットテストとエンドツーエンドテストの両方に十分なサポートを提供しています.AngularJSアプリケーションの作成と同時に、作成テストは、将来バグを修正する必要がある時間を大幅に節約できます.本稿では、AngularJSでユニットテストとエンドツーエンドテストを記述する方法.
Jasmineをテストフレームワークとして使用し、karmaをテスト実行期間として使用します.
まだテスト環境がない場合は、まず次の手順に従います.
Nodeをダウンロードしてインストールします.jsはnpmインストールkarma(npm install-g karma)を使用してGithubから本明細書のdemoをダウンロードし、解凍後のファイルの解凍を完了します.test/unitとtest/e 2 eディレクトリの下でテストファイルを見つけることができます.テスト結果を表示するにはscript/testを実行します.Batファイルは、Karmaサーバを開きます.私たちの主なHTMLファイルはapp/notesです.html、あなたは通過することができますhttps://localhost/angular-seed/app/notes.htmlにアクセスします.
ユニットテストの開始
まず、ユニットテストを開発プロセスに組み込む方法を見て、簡単なAngularアプリケーションを作成します.そこで、アプリケーションを作成し、ユニットテストを各コンポーネントに適用します.このセクションでは、ユニットテストの作成方法について学習します.
Controller Directives Filters Factories非常に簡単なtodoアプリケーションを作成します.タグにはテキスト入力ボックスが含まれており、ユーザーはここで簡単なメモを書くことができます.ユーザがボタンを押すと、このノートはノートリストに追加されます.HTMLのLocalStorageを使ってメモ情報を保存します.初期のHTMLコードは以下の通りです.私たちの同僚はBootstrapを使ってレイアウトを作成します.
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html ng-app="todoApp"> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.min.js" type="text/javascript"></script> <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js" type="text/javascript"></script> <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css" type="text/css"/> <script type="text/javascript" src="js/app.js"></script> <style> .center-grey{ background:#f2f2f2; margin-top:20; } .top-buffer { margin-top:20px; } button{ display: block; width: 100%; } </style> <title>Angular Todo Note App</title> </head> <body> <div class="container center-grey" ng-controller="TodoController"> <div class="row top-buffer" > <span class="col-md-3"></span> <span class="col-md-5"> <input class="form-control" type="text" ng-model="note" placeholder="Add a note here"/> </span> <span class="col-md-1"> <button ng-click="createNote()" class="btn btn-success">Add</button> </span> <span class="col-md-3"></span> </div> <div class="row top-buffer" > <span class="col-md-3"></span> <span class="col-md-6"> <ul class="list-group"> <li ng-repeat="note in notes track by $index" class="list-group-item"> <span>{{note}}</span> </li> </ul> </span> <span class="col-md-3"></span> </div> </div> </body> </html> 

上のコードでは、ご覧のように、私たちのAngularモジュールはtodoAppで、コントローラのときTodoControllerです.入力ボックスはnoteモデルにバインドされています.追加されたノート系のそれを示すリストもありますか.また、ボタンをクリックすると、TodoControllerのcreateNote()関数が呼び出され、appが開く.jsファイルを使用して、モデルとコントローラを作成します.夏敏のコードをappに追加します.jsファイル:
var todoApp = angular.module('todoApp',[]);

todoApp.controller('TodoController', function($scope, notesFactory) {
  $scope.notes = notesFactory.get();
  $scope.createNote = function() {
    notesFactory.put($scope.note);
    $scope.note = '';
    $scope.notes = notesFactory.get();
  }
});

todoApp.factory('notesFactory', function() {
  return {
    put: function(note) {
      localStorage.setItem('todo' + (Object.keys(localStorage).length + 1), note);
    },
    get: function() {
      var notes = [];
      var keys = Object.keys(localStorage);

      for(var i = 0; i < keys.length; i++) {
        notes.push(localStorage.getItem(keys[i]));
      }

      return notes;
    }
  };
});

私たちのTodoControllerはnotesFactoryというfactoryサービスを使ってノート情報を保存して抽出しています.createNote()関数が呼び出されると、このfactoryサービスを使用してlocalStorageに情報を保存し、noteモデルを空にします.したがって、ユニットテストを行う場合は、コントローラが初期化されたときにscopeに一定数のノートが含まれていることを確認する必要があります.createNote()関数を呼び出すと、ノートの数は前の数より1つ増えるはずです.ユニットテストを行うコードは次のとおりです.
describe('TodoController Test', function() {
  beforeEach(module('todoApp')); // will be run before each it() function

  // we don't need the real factory here. so, we will use a fake one.
  var mockService = {
    notes: ['note1', 'note2'], //just two elements initially
    get: function() {
      return this.notes;
    },
    put: function(content) {
      this.notes.push(content);
    }
  };

  // now the real thing: test spec
  it('should return notes array with two elements initially and then add one',
    inject(function($rootScope, $controller) { //injects the dependencies
      var scope = $rootScope.$new();

      // while creating the controller we have to inject the dependencies too.
      var ctrl = $controller('TodoController', {$scope: scope, notesFactory:mockService});

      // the initial count should be two
      expect(scope.notes.length).toBe(2);

      // enter a new note (Just like typing something into text box)
      scope.note = 'test3';

      // now run the function that adds a new note (the result of hitting the button in HTML)
      scope.createNote();

      // expect the count of notes to have been increased by one!
      expect(scope.notes.length).toBe(3);
    })
  );
});

describe()メソッドは、テストキットを定義します.その役割は、すべてのテストをこのスイートに含めることです.ここでは,すべてのit()の前にbeforeEach()関数を実行した.各it()関数は私たちのテストspecであり、その中で本当のテストが行われています.そのため、すべてのテストが実行される前に、モジュールをロードする必要があります.
これはユニットテストなので、外部依存は必要ありません.私たちのコントローラがnoteFactoryに依存してノートを処理していることを知っています.したがって、コントローラを単一クラウドテストするには、偽のfactoryまたはserviceを使用する必要があります.これは、同じ関数、get()、put()を含む、本物のnoteFactoryをシミュレートするためにmokeServiceを作成する理由です.私たちの本当のfactoryはlocalStorageを使ってメモを保存していますが、この偽のfactoryは埋め込まれた配列を使用しています.
では、it()関数がどのようにテストに使われているのかを見てみましょう.Angularによって自動的に注入できる2つの依存項目rootScopeとcontrollerが宣言されているのがわかります.2つのサービスは、ルート役割ドメインを取得し、新しいコントローラを作成するために使用されます.
コントローラサービスには2つのパラメータが必要です.最初のパラメータは、作成するコントローラの名前です.2番目のパラメータは、コントローラ依存項目を表すオブジェクトです.rootScope.$New()メソッドは、コントローラに注入するための新しい自己作用を返します.同僚もコントローラに定義した偽factoryを渡していることに気づきました.
今、expect(scope.notes.length).toBe(2)はコントローラでscopeを初期化する.notesの場合は断言します.ノートの数が2つ以上ある場合、テストは失敗します.同様に,新しいnoteモデルを生成しcreateNote()関数を実行し,新しいメモ項目を追加すると予想した.今expect(scope.notes.length)です.TOBe(3)は結果をチェックします.初期化時に2つのプロジェクトを追加したので、3つのプロジェクトがあるはずです.karmaでテストに成功したか失敗したかを確認できます.
Factoryのテスト
次にfactoryのユニットテストを行い、予想通りに動作することを確認します.テストのコードは次のとおりです.
describe('notesFactory tests', function() {
  var factory;

  // excuted before each "it()" is run.
  beforeEach(function() {
    // load the module
    module('todoApp');

    // inject your factory for testing
    inject(function(notesFactory) {
      factory = notesFactory;
    });

    var store = {
      todo1: 'test1',
      todo2: 'test2',
      todo3: 'test3'
    };

    spyOn(localStorage, 'getItem').andCallFake(function(key) {
      return store[key];
    });

    spyOn(localStorage, 'setItem').andCallFake(function(key, value) {
      return store[key] = value + '';
    });

    spyOn(localStorage, 'clear').andCallFake(function() {
      store = {};
    });

    spyOn(Object, 'keys').andCallFake(function(value) {
      var keys=[];

      for(var key in store) {
        keys.push(key);
      }

      return keys;
    });
  });

  // check to see if it has the expected function
  it('should have a get function', function() {
    expect(angular.isFunction(factory.get)).toBe(true);
    expect(angular.isFunction(factory.put)).toBe(true);
  });

  //check to see if it returns three notes initially
  it('should return three todo notes initially', function() {
    var result = factory.get();

    expect(result.length).toBe(3);
  });

  //check if it successfully adds a new item
  it('should return four todo notes after adding one more', function() {
    factory.put('Angular is awesome');

    var result = factory.get();
    expect(result.length).toBe(4);
  });
});

テストの手順は、前述したTodoControllerのテストと似ています.実際のfactoryではlocalStorageを使用してメモ項目を格納および抽出します.しかし、ユニットテストを行っているので、外部サービスに依存したくありません.そのため、localStorageを変換する必要があります.getItem()およびlocalStorage.serItem()は、本物のlocalStorage関数ではなく偽の関数です.spuOn(localStorage,’setItem’).andCallFake()はこのことをするために使われています.spyOn関数の最初のパラメータは私たちが興味を持っているオブジェクトを示し、2番目のパラメータは私たちが監視する必要がある関数を示しています.andCallfake()はオーウェンたちに自分の関数を書くことができるようにした.そこで、このテストではlocalStorageで使用する関数の構成を完了しました.私たちのfactoryではObjectも使います.keys()関数を反復してノートの総数を取得します.そのため、この簡単な例ではObjectにも対応できます.keys(localStorage)は、localStorageではなく、私たちの配列の値を監視して返します.
次に,このfactoryに必要な関数(get()とput()が含まれているかどうかを調べた.これはangularを通じてできます.isFunction()関数で完了します.そしてこのfactoryで3つのノートを初期化したことを確認しました.最後に新しいノートを追加し、ノートの数を追加したと断言しました.
フィルタのテスト
次に、ノートがページに表示される方法を変更する必要があるとします.1つのノートの文字数が20文字を超えたら、最初の10文字しか表示されません.簡単なフィルタtruncateを作成してみましょう
todoApp.filter('truncate', function() {
  return function(input,length) {
    return (input.length > length ? input.substring(0, length) : input );
  };
});

ページタグでは、次のように使用します.
{{note | truncate:20}} 

このフィルタをユニットテストするには、次のコードを記述します.
describe('filter tests', function() { beforeEach(module('todoApp')); it('should truncate the input to 10 characters', //this is how we inject a filter by appending Filter to the end of the filter name inject(function(truncateFilter) { expect(truncateFilter('abcdefghijkl', 10).length).toBe(10); }) ); });

命令をテスト
このコマンドをバインドする要素に背景色を追加できる簡単なコマンドを作成します.これはCSSで簡単にできます.しかし、命令をテストする方法を学ぶために、次のコードを作成しました.
todoApp.directive('customColor', function() {
  return {
    restrict: 'A',
    link: function(scope, elem, attrs) {
      elem.css({'background-color': attrs.customColor});
    }
  };
});

この命令は、ul custom−color="rgb(128,128,128)"</ul<のような任意の要素に適用することができる.テストコードは次のとおりです.
describe('directive tests', function() {
    beforeEach(module('todoApp'));
  it('should set background to rgb(128, 128, 128)',
    inject(function($compile,$rootScope) {
      scope = $rootScope.$new();

      // get an element representation
      elem = angular.element("<span custom-color=\"rgb(128, 128, 128)\">sample</span>");

      // create a new child scope
      scope = $rootScope.$new();

      // finally compile the HTML
      $compile(elem)(scope);

      // expect the background-color css property to be desirabe one
      expect(elem.css("background-color")).toEqual('rgb(128, 128, 128)');
     })
  );
});

実際のコンパイルを完了し、テストしたい要素をテストするために$compileというサービスが必要です.angular.element()は、jqLiteまたはjQuery(使用可能な場合)要素を作成します.次に、この要素を役割ドメインにコンパイルし、テストできます.上記の例では、background-color属性がrgb(128,128,128)に等しいことを望んでいます.
AngularでのE 2 Eテストの使用
E 2 Eテストでは、コンポーネントの山を合わせて、予想通りに全プロセスが動作しているかどうかを確認します.私たちの例では、ユーザーがテキスト入力ボックスにテキストを入力してボタンをクリックすると、localStorageに情報が追加され、テキストボックスの下のリストに表示されることを確認します.
E 2 EテストではAngularのシーンランチャーが使用されます.demoアプリケーションをダウンロードして解凍が完了した場合、test/e 2 eにrunnerがあることがわかります.htmlファイル.これが私たちのシーン実行ファイルです.scenario.jsファイルにはe 2 eテストが含まれています(ここでテストを作成する必要があります).テストを作成した後、実行できます.http://localhost/angular-seed/test/e2e/runner.htmlに表示されます.E 2 Eテストのコードは以下の通りです.
describe('my app', function() { beforeEach(function() { browser().navigateTo('../../app/notes.html'); }); var oldCount = -1; it("entering note and performing click", function() { element('ul').query(function($el, done) { oldCount = $el.children().length; done(); }); input('note').enter('test data'); element('button').query(function($el, done) { $el.click(); done(); }); }); it('should add one more element now', function() { expect(repeater('ul li').count()).toBe(oldCount + 1); }); });


説明する
テストを行うときは、まず主なHTMLページ、app/notesにナビゲートする必要があります.html.これはbrowserを通じてできます.navigateTo()で完成します.element.query()関数はul要素を選択し、その中に何個の初期化項目があるかを記録します.この調製はoldCount変数に格納される.次に,テキストボックスにメモを入力し,input(’note’)enter()で完了することをシミュレートした.モデルの名前をinput関数に渡す必要があることに注意してください.私たちのHTMLページではinputがng-model=noteにバインドされています.したがって、モデル名は入力フィールドを識別するために使用されるべきです.次にボタンをクリックし、リストに新しいメモ(li要素)が追加されているかどうかを確認します.新しいメモの数と古いメモの数を比較することで結論を出すことができます.
まとめ
AngularJSアプリケーションの開発プロセスとテストは、特にTDDに分けられないようです.最初は時間がかかるように見えるテストは、必ず最後にバグを修復する時間を節約します.
本文はUnit and End to End Testing in AngularJSから訳して、原文の住所http://www.sitepoint.com/unit-and-e2e-testing-in-angularjs/