您可能已经知道,我们中许多拥有大量书面单元测试的人都遇到了这个不容易解决的问题。根据AngularJs 单元测试指南,我使用Jasmine语法编写了大约3500多个单元测试。测试是使用KarmaRunner执行的。
问题是由于内存泄漏,它们无法一次全部执行。在运行它们时,无论在哪个浏览器上运行它们,内存都会累积,并且有时浏览器崩溃并断开连接。我现在知道的最好的解决方法是,在社区中使用的有此问题的方法是将测试分成多个运行,最后通过合并单个运行的结果来获得正确的覆盖范围。
当我第一次遇到这个问题时,我进行了大约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开始 之前运行:
$ 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(); }); });
有两件事需要清理:
他们两个都很棘手,很难找出并考虑在内。对于第一个,我已经知道了,但是直到发现第二个与Jasmine的内部工作方式有关的第二步,它并没有太大帮助。我在他们的GitHub存储库上创建了一个问题,该问题应该有助于找到更好的解决方案,或者至少可以更快地在开发人员之间传播这些信息。
我希望这个答案对很多有此问题的人有所帮助。完成所有其他测试的重构后,我也会写一些信息。
干杯!