AngularJs单元测试内存泄漏

S.K*_*ski 25 unit-testing memory-leaks jasmine angularjs karma-runner

正如您可能已经知道的那样,我们许多拥有大量书面单元测试的人已经遇到了这个非常简单的可解决问题.在AngularJs 单元测试指南之后,我有大约3500多个用Jasmine语法编写的单元测试.测试由Karma runner 执行.

问题是由于一些内存泄漏,它们无法一次性执行.在运行它们时,无论在哪个浏览器上运行,内存都会建立起来,并且在某些时候浏览器会崩溃并断开连接.我现在所知道的最好的解决方法是在社区中使用这个问题,在多次运行中拆分测试,最后通过合并单次运行的结果来获得正确的覆盖率.

当我第一次遇到这个问题时,我进行了大约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; }
  };
});
Run Code Online (Sandbox Code Playgroud)

之后我有一个简单的指令,它使用这个服务来初始化许多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());
    }
  };
});
Run Code Online (Sandbox Code Playgroud)

最后,我动态注册1000个测试套件,其中包含指令的测试定义,其中btw按照Angular 单元测试指南中的建议编写.

// define multiple suits with the same definition just for showcase
for (var i = 0; i < 1000; i += 1) {
  describe('heavyLoad directive #' + i, testDefinition);
}
Run Code Online (Sandbox Code Playgroud)

要尝试该示例,只需从GitHub签出项目,然后再运行karma start run:

$ npm install
$ bower install
Run Code Online (Sandbox Code Playgroud)

我期待找到问题所在并最终解决问题.

干杯

S.K*_*ski 21

问题在于每次测试后需要进行的遗忘清理.添加之后,测试次数不再重要,因为内存消耗稳定,测试可以在任何浏览器中运行.

我已经加入了先前的测试定义的修改在这里显示与成功执行3000个dinamically注册的测试解决方案.

以下是测试现在的样子:

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();
    });

});
Run Code Online (Sandbox Code Playgroud)

有两件事需要清理:

  • 使用$ compile进行测试指令时编译的元素
  • describe函数范围内的所有变量

这两个是棘手的,很难找到并考虑到.对于我已经知道的第一个,但是在我发现第二个与Jasmine如何在里面工作有关之前,它没有多少帮助.我在他们的GitHub存储库上创建了一个问题,该问题应该有助于找到更好的解决方案,或者至少可以更快地在开发人员之间传

我希望这个答案对很多人有这个问题很有帮助.在完成所有其他测试后,我也会写一些信息.

干杯!

  • 最重要的是,从1.5.1开始,您可以使用`beforeAll`而不是`beforeEach`来模拟每个描述一次的模块,它应该可以进一步提高性能.https://docs.angularjs.org/guide/unit-testing#using-beforeall- (2认同)
  • @ S.Klechkovski谢谢你的这篇文章!与Karma + mocha和所有`$ compiled`元素上的element.remove()有相同的问题解决了我的问题! (2认同)