AngularJs 单元测试内存泄漏
AngularJs unit testing memory leaks
正如您可能已经知道的那样,我们中的许多人都有大量的书面单元测试遇到过这个不容易解决的问题。我在 Jasmine syntax following the AngularJs unit testing guide. The tests are executed with Karma 运行ner.
中编写了大约 3500 多个单元测试
问题是由于某些内存泄漏,它们无法一次全部执行。当 运行 连接它们时,无论它们在什么浏览器上 运行 连接它们,内存都会增加,并且在某些时候浏览器会崩溃并断开连接。我现在知道的最好的解决方法是在有这个问题的社区中使用将测试分成多个 运行s,最后通过合并单个 [=45] 的结果来获得正确的覆盖范围=]s.
当我第一次遇到这个问题时,我进行了大约 1000 次测试。在为 运行ning 尝试了所有可用的浏览器之后,我将测试拆分为多个 运行s,但事实证明这在很长一段时间内都不是好的解决方法。现在,测试在 14 个以上的 运行 中执行,这些 运行 是并行执行的,以减少完成时间,但在我看来,这仍然不能永久解决问题,但由于资源限制会稍微延迟一点(RAM, CPU) 和烦人的时间消耗。
有人可能会争辩说我的代码中存在内存泄漏,即使在浏览器中 运行 运行应用程序时我没有遇到任何类似的问题,我也无法保证。这就是为什么我创建了一个示例项目来突出这个问题。
为了重现这个问题,我创建了一个 Angular service,它的内存消耗很大,如下所示:
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; }
};
});
之后我有一个简单的 directive,它使用此服务来初始化许多 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());
}
};
});
最后,我使用 test definition for the directive which btw is written as suggested in the Angular unit testing 指南动态注册了 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 和 运行ning karma start 运行:
之前的项目
$ npm install
$ bower install
期待找到问题所在并最终解决。
干杯
问题在于忘记了每次测试后需要进行的清理工作。
添加它之后测试的数量不再重要,因为内存消耗稳定并且测试可以在任何浏览器中运行。
我添加了对先前测试定义的修改 here,它显示了成功执行 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();
});
});
有两件事需要清理:
- 使用 $compile 测试指令时的编译元素
- 描述函数范围内的所有变量
他们两个很棘手,很难发现和考虑。
对于第一个我已经知道但它并没有多大帮助,直到我发现了第二个与 Jasmine 内部工作方式有关的第二个。
我在他们的 GitHub 存储库上创建了一个 issue,这应该有助于找到更好的解决方案,或者至少可以更快地在开发人员中传播这些信息。
我希望这个答案对很多遇到这个问题的人有所帮助。在我完成所有其他测试的重构后,我也会写一些信息。
干杯!
正如您可能已经知道的那样,我们中的许多人都有大量的书面单元测试遇到过这个不容易解决的问题。我在 Jasmine syntax following the AngularJs unit testing guide. The tests are executed with Karma 运行ner.
中编写了大约 3500 多个单元测试问题是由于某些内存泄漏,它们无法一次全部执行。当 运行 连接它们时,无论它们在什么浏览器上 运行 连接它们,内存都会增加,并且在某些时候浏览器会崩溃并断开连接。我现在知道的最好的解决方法是在有这个问题的社区中使用将测试分成多个 运行s,最后通过合并单个 [=45] 的结果来获得正确的覆盖范围=]s.
当我第一次遇到这个问题时,我进行了大约 1000 次测试。在为 运行ning 尝试了所有可用的浏览器之后,我将测试拆分为多个 运行s,但事实证明这在很长一段时间内都不是好的解决方法。现在,测试在 14 个以上的 运行 中执行,这些 运行 是并行执行的,以减少完成时间,但在我看来,这仍然不能永久解决问题,但由于资源限制会稍微延迟一点(RAM, CPU) 和烦人的时间消耗。
有人可能会争辩说我的代码中存在内存泄漏,即使在浏览器中 运行 运行应用程序时我没有遇到任何类似的问题,我也无法保证。这就是为什么我创建了一个示例项目来突出这个问题。
为了重现这个问题,我创建了一个 Angular service,它的内存消耗很大,如下所示:
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; }
};
});
之后我有一个简单的 directive,它使用此服务来初始化许多 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());
}
};
});
最后,我使用 test definition for the directive which btw is written as suggested in the Angular unit testing 指南动态注册了 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 和 运行ning karma start 运行:
之前的项目$ npm install
$ bower install
期待找到问题所在并最终解决。
干杯
问题在于忘记了每次测试后需要进行的清理工作。 添加它之后测试的数量不再重要,因为内存消耗稳定并且测试可以在任何浏览器中运行。
我添加了对先前测试定义的修改 here,它显示了成功执行 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();
});
});
有两件事需要清理:
- 使用 $compile 测试指令时的编译元素
- 描述函数范围内的所有变量
他们两个很棘手,很难发现和考虑。 对于第一个我已经知道但它并没有多大帮助,直到我发现了第二个与 Jasmine 内部工作方式有关的第二个。 我在他们的 GitHub 存储库上创建了一个 issue,这应该有助于找到更好的解决方案,或者至少可以更快地在开发人员中传播这些信息。
我希望这个答案对很多遇到这个问题的人有所帮助。在我完成所有其他测试的重构后,我也会写一些信息。
干杯!