AngularJs单元测试内存泄漏
正如您可能已经知道的,我们中有很多人都有大量的书面单元测试,他们遇到了这个不容易解决的问题。我有大约3500多个单元测试是按照AngularJs指南的语法编写的。使用runner执行测试 问题是,由于一些内存泄漏,它们不能一次全部执行。在运行它们时,无论它们运行在哪个浏览器上,内存都会累积,并且在某个时候浏览器会崩溃和断开连接。到目前为止,我所知道的最好的解决方法是在有此问题的社区中使用,将测试拆分为多个运行,最后通过合并单个运行的结果来获得正确的覆盖率 当我第一次遇到这个问题时,我进行了大约1000次测试。在尝试使用所有可用的浏览器运行之后,我将测试分为多个运行,但是很长一段时间以来,这并不是一个好的解决方法。现在,测试在14次以上的单次运行中执行,这些运行是并行运行的,以减少完成时间,但在我看来,这无法永久解决问题,但由于资源限制(RAM、CPU)和恼人的时间消耗,会稍微延迟一点 有人可能会争辩说,我的代码中存在内存泄漏,对此我无法保证,即使我在浏览器中运行应用程序时没有任何问题。这就是为什么我创建了一个示例项目来突出这个问题 为了重现这个问题,我创建了一个角度,它消耗了大量内存,如下所示:AngularJs单元测试内存泄漏,angularjs,unit-testing,memory-leaks,jasmine,karma-runner,Angularjs,Unit Testing,Memory Leaks,Jasmine,Karma Runner,正如您可能已经知道的,我们中有很多人都有大量的书面单元测试,他们遇到了这个不容易解决的问题。我有大约3500多个单元测试是按照AngularJs指南的语法编写的。使用runner执行测试 问题是,由于一些内存泄漏,它们不能一次全部执行。在运行它们时,无论它们运行在哪个浏览器上,内存都会累积,并且在某个时候浏览器会崩溃和断开连接。到目前为止,我所知道的最好的解决方法是在有此问题的社区中使用,将测试拆分为多个运行,最后通过合并单个运行的结果来获得正确的覆盖率 当我第一次遇到这个问题时,我进行了大约1
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());
}
};
});
我期待着找到问题所在并最终解决它
干杯问题在于每次测试后需要进行的遗忘清理。 添加后,测试的数量不再重要,因为内存消耗是稳定的,测试可以在任何浏览器中运行 我已经添加了对先前测试定义的修改,它显示了成功执行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();
});
});
description('testSuite',函数(){
var-suite={};
在每个(模块(“应用”)之前;
beforeach(注入函数($rootScope,$compile,heavyLoad){
套件。$rootScope=$rootScope;
套件。$compile=$compile;
suite.heavyLoad=heavyLoad;
suite.$scope=$rootScope.$new();
spyOn(suite.heavyLoad,'getHeavyString')。和.callThrough();
spyOn(suite.heavyLoad,'getHeavyObject')和.callThrough();
spyOn(suite.heavyLoad,'getHeavyList')和.callThrough();
}));
//注:清理
在每个(函数()之后){
//注意:防止DOM元素泄漏
element.remove();
});
毕竟(功能){
//注意:防止由于为创建JavaScript闭包而导致内存泄漏
//jasmine语法(beforeach,afterEach,beforeAll,aftereall,it.)。
suite=null;
});
suite.compileDirective=函数(模板){
suite.element=suite.$compile(模板)(suite.$scope);
suite.directiveScope=suite.element.isolateScope();
suite.directiveController=suite.element.controller('heavyLoad');
};
它('应该正确编译',函数(){
//给定
var givenTemplate='';
//什么时候
compileDirective(givenTemplate);
//然后
expect(suite.directiveScope.title).toBeDefined();
expect(suite.directiveScope.items).toBeDefined();
expect(suite.heavyLoad.getHeavyString).tohavebeincall();
expect(suite.heavyLoad.getHeavyList).tohavebeincall();
});
});
有两件事需要清理:
- 使用$compile进行测试指令时的compiled元素
- 描述函数范围中的所有变量
干杯 成功重构后,约2分钟内执行约4000个测试。您是如何做到这一点的?我已经使用
this
为Jasmine的清理设置了以前的测试,但是在运行了600个测试之后,运行程序在PhantomJS上失败了。同样的情况也会发生,但在Chrome上运行的测试更少。除此之外,从1.5.1开始,您可以使用beforeAll
而不是beforeach
来模拟每个模块一次,这样可以进一步提高性能@维瓦斯科谢谢你,我直到现在才意识到这一点。是的,这会稍微提高性能,因为$injector不会在每次测试之前重新创建,但应该小心使用,因为某些服务有时会保持某些状态,这可能会影响测试用例。@S.Klechkovski感谢您的这篇文章!所有$compiled
元素上的Karma+mocha和元素都有相同的问题。remove()修复了我的问题!
$ npm install
$ bower install
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();
});
});