ご存知かもしれませんが、大量の単体テストを作成している私たちの多くは、この簡単に解決できない問題に直面しています。 AngularJs ユニットテスト ガイドに従って Jasmine 構文で記述された約3500以上のユニットテストがあります。テストは カルマ ランナーで実行されます。
問題は、メモリリークが原因で一度に実行できないことです。それらを実行している間、それらが実行されているブラウザに関係なくメモリが蓄積され、ある時点でブラウザがクラッシュして切断されます。この問題を抱えているコミュニティで使用されている、私が今知っている最善の回避策は、テストを複数の実行に分割し、最後に単一の実行からの結果をマージすることによって正しいカバレッジを取得することです。
この問題に最初に遭遇したとき、私は約1000回のテストを行いました。実行可能なすべてのブラウザーで試した後、テストを複数の実行に分割しましたが、これは長い間適切な回避策ではないことが判明しました。現在、テストは14回以上のシングルランで実行され、完了までの時間を短縮しますが、IMOでも問題を永続的に解決することはできませんが、リソースの制限(RAM、CPU)と煩わしい時間の消費のために少し遅れます。
ブラウザでアプリケーションを実行するときに同様の問題が発生していなくても、コードにメモリリークがあり、保証できないと誰かが主張する可能性があります。そのため、この問題を浮き彫りにするサンプルプロジェクトを作成しました。
この問題を再現するために、次のようにメモリ消費量が多いAngular サービス を作成しています。
app.factory('heavyLoad', function () {
// init
var heavyList = [];
var heavyObject = {};
var heavyString = '';
// populate..
return {
getHeavyList: function () { return heavyList; },
getHeavyObject: function () { return heavyObject; },
getHeavyString: function () { return heavyString; }
};
});
その後、このサービスを使用して多くのDOM要素を初期化する単純な ディレクティブ があります。
app.directive('heavyLoad', function (heavyLoad) {
return {
scope: {},
template: '' +
'<div>' +
' <h1>{{title}}</h1>' +
' <div ng-repeat="item in items">' +
' <div ng-repeat="propData in item">' +
' <p>{{propData}}</p>' +
' </div>' +
' </div>' +
'</div>',
link: function (scope, element) {
scope.items = heavyLoad.getHeavyList();
scope.title = heavyLoad.getHeavyString();
// add data to the element
element.data(heavyLoad.getHeavyList());
}
};
});
そして最後に、Angular ユニットテスト ガイドで提案されているようにbtwが記述されているディレクティブの テスト定義 を使用して1000個のテストスイートを動的に登録しています。
// define multiple suits with the same definition just for showcase
for (var i = 0; i < 1000; i += 1) {
describe('heavyLoad directive #' + i, testDefinition);
}
例を試すには、 GitHub からプロジェクトをチェックアウトし、karma startを実行する前に実行します。
$ npm install
$ bower install
問題がどこにあるのかを見つけて、最終的に解決するのを楽しみにしています。
乾杯
問題は、各テストの後に実行する必要があるクリーンアップを忘れたことにありました。追加後は、メモリ消費量が安定しており、どのブラウザでもテストを実行できるため、テストの数は重要ではなくなります。
以前のテスト定義の変更を追加しました ここ これは、3000の動的に登録されたテストを正常に実行したソリューションを示しています。
テストは次のようになります。
describe('testSuite', function () {
var suite = {};
beforeEach(module('app'));
beforeEach(inject(function ($rootScope, $compile, heavyLoad) {
suite.$rootScope = $rootScope;
suite.$compile = $compile;
suite.heavyLoad = heavyLoad;
suite.$scope = $rootScope.$new();
spyOn(suite.heavyLoad, 'getHeavyString').and.callThrough();
spyOn(suite.heavyLoad, 'getHeavyObject').and.callThrough();
spyOn(suite.heavyLoad, 'getHeavyList').and.callThrough();
}));
// NOTE: cleanup
afterEach(function () {
// NOTE: prevents DOM elements leak
suite.element.remove();
});
afterAll(function () {
// NOTE: prevents memory leaks because of JavaScript closures created for
// jasmine syntax (beforeEach, afterEach, beforeAll, afterAll, it..).
suite = null;
});
suite.compileDirective = function (template) {
suite.element = suite.$compile(template)(suite.$scope);
suite.directiveScope = suite.element.isolateScope();
suite.directiveController = suite.element.controller('heavyLoad');
};
it('should compile correctly', function () {
// given
var givenTemplate = '<div heavy-load></div>';
// when
suite.compileDirective(givenTemplate);
// then
expect(suite.directiveScope.title).toBeDefined();
expect(suite.directiveScope.items).toBeDefined();
expect(suite.heavyLoad.getHeavyString).toHaveBeenCalled();
expect(suite.heavyLoad.getHeavyList).toHaveBeenCalled();
});
});
クリーンアップする必要があるものが2つあります。
それらの2つはトリッキーで、見つけて考慮するのが困難です。最初のものについては私はすでに知っていましたが、ジャスミンが内部でどのように機能するかに関連する2番目のものを発見するまでそれはあまり役に立ちませんでした。私は彼らのGitHubリポジトリに issue を作成しました。これは、より良い解決策を見つけるのに役立つか、少なくともこの情報を開発者の間でより速く広めるのに役立つはずです。
この回答が、この問題を抱えている多くの人々に役立つことを願っています。他のすべてのテストのリファクタリングが終了したら、いくつかの情報も書き込みます。
乾杯!