当前位置: 首页 > 面试题库 >

AngularJs单元测试内存泄漏

夏意蕴
2023-03-14
问题内容

您可能已经知道,我们中许多拥有大量书面单元测试的人都遇到了这个不容易解决的问题。根据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();
    });

});

有两件事需要清理:

  • 将$ compile用于测试指令时的已编译元素
  • describe函数范围中的所有变量

他们两个都很棘手,很难找出并考虑在内。对于第一个,我已经知道了,但是直到发现第二个与Jasmine的内部工作方式有关的第二步,它并没有太大帮助。我在他们的GitHub存储库上创建了一个问题,该问题应该有助于找到更好的解决方案,或者至少可以更快地在开发人员之间传播这些信息。

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

干杯!



 类似资料:
  • 本文向大家介绍AngularJS 单元测试服务,包括了AngularJS 单元测试服务的使用技巧和注意事项,需要的朋友参考一下 示例 服务编号 考试 跑!

  • 问题内容: 正如我们在http://docs.angularjs.org/tutorial/step_07中看到的那样, 建议通过e2e测试来完成路由测试, 但是,我认为’$ routeProvider’配置是通过单个函数function($ routeProvider)完成的,我们应该能够在不涉及浏览器的情况下进行单元测试,因为我认为路由功能不需要浏览器DOM。 例如, 当url为/ foo时,

  • 本文向大家介绍AngularJS 单元测试过滤器,包括了AngularJS 单元测试过滤器的使用技巧和注意事项,需要的朋友参考一下 示例 过滤器代码: 考试: 跑! 备注:在inject测试的调用中,您的过滤器需要使用其名称+ Filter来指定。原因是,每当您为模块注册过滤器时,Angular都会Filter在其名称后面附加一个注册它。

  • 本文向大家介绍AngularJS 单元测试组件(1.5+),包括了AngularJS 单元测试组件(1.5+)的使用技巧和注意事项,需要的朋友参考一下 示例 组件代码: 考试: 跑!

  • 本文向大家介绍AngularJS 单元测试控制器,包括了AngularJS 单元测试控制器的使用技巧和注意事项,需要的朋友参考一下 示例 控制器代码: 考试: 跑!

  • 问题内容: 我有一个在django中运行的小型多线程脚本,随着时间的流逝,它开始使用越来越多的内存。将其保留一整天会消耗大约6GB的RAM,我开始进行交换。 在http://www.lshift.net/blog/2008/11/14/tracing-python-memory- leaks 之后,我将其视为最常见的类型(仅使用800M内存): 这没有什么奇怪的。我现在应该怎么做才能帮助调试内存问