AngularJs 单元测试内存泄漏 [英] AngularJs unit testing memory leaks

查看:29
本文介绍了AngularJs 单元测试内存泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

您可能已经知道,我们中许多编写了大量单元测试的人都遇到过这个不容易解决的问题.我有大约 3500 多个单元测试以 Jasmine 语法编写,遵循 AngularJs 单元测试 指南.测试使用 Karma runner 执行.

问题是由于一些内存泄漏,它们不能一次全部执行.在运行它们时,无论它们在什么浏览器上运行,内存都会增加,并且在某些时候浏览器会崩溃并断开连接.目前我所知道的最好的解决方法是将测试拆分为多次运行,最后通过合并单次运行的结果来获得正确的覆盖范围.

当我第一次遇到这个问题时,我进行了大约 1000 次测试.在尝试运行所有可用的浏览器后,我将测试拆分为多次运行,但事实证明,这在很长一段时间内都不是好的解决方法.现在,测试在 14 次以上的单次运行中执行,这些运行并行运行以减少完成时间,但 IMO 仍然无法永久解决问题,但由于资源限制(RAM、CPU)和烦人的时间消耗而延迟了一点点.

有人可能会争辩说我的代码中存在内存泄漏,即使我在浏览器中运行应用程序时没有任何类似的问题,我也无法保证.这就是为什么我创建了一个示例项目来突出这个问题.

为了重现这个问题,我正在创建一个 Angular service 内存消耗很大,如下所示:

app.factory('heavyLoad', function () {//在里面var HeavyList = [];var HeavyObject = {};var HeavyString = '';//填充..返回 {getHeavyList: function () { returnheavyList;},getHeavyObject: 函数 () { 返回重对象;},getHeavyString: 函数 () { 返回重字符串;}};});

之后我有一个简单的指令 使用此服务初始化许多 DOM 元素:

app.directive('heavyLoad', function (heavyLoad) {返回 {范围: {},模板:'' +'

'+' <h1>{{title}}</h1>'+' <div ng-repeat="项目中的项目">'+' <div ng-repeat="propData in item">'+' <p>{{propData}}</p>'+' </div>'+' </div>'+'</div>',链接:功能(范围,元素){scope.items =heavyLoad.getHeavyList();scope.title = HeavyLoad.getHeavyString();//向元素添加数据element.data(heavyLoad.getHeavyList());}};});

最后,我使用 测试定义 顺便说一下,按照 Angular 单元测试 中的建议编写的指令a> 指南.

//定义多个相同定义的套装,仅供展示for (var i = 0; i <1000; i += 1) {describe('heavyLoad 指令#' + i, testDefinition);}

要尝试该示例,只需在运行 karma start<之前从 GitHub 签出项目/em> 运行:

$ npm install$凉亭安装

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

干杯

解决方案

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

我添加了对之前测试定义的修改 此处 显示了成功执行 3000 次动态注册测试的解决方案.

测试现在的样子:

describe('testSuite', function () {var 套件 = {};beforeEach(module('app'));beforeEach(注入(函数($rootScope,$compile,heavyLoad){Suite.$rootScope = $rootScope;Suite.$compile = $compile;Suite.heavyLoad = 重载;Suite.$scope = $rootScope.$new();spyOn(suite.heavyLoad, 'getHeavyString').and.callThrough();spyOn(suite.heavyLoad, 'getHeavyObject').and.callThrough();spyOn(suite.heavyLoad, 'getHeavyList').and.callThrough();}));//注意:清理afterEach(函数(){//注意:防止 DOM 元素泄漏套件.element.remove();});毕竟(功能(){//注意:防止因 JavaScript 闭包而导致的内存泄漏//茉莉花语法(beforeEach、afterEach、beforeAll、afterAll、it..).套件 = 空;});Suite.compileDirective = 函数(模板){Suite.element = suite.$compile(template)(suite.$scope);Suite.directiveScope = suite.element.isolateScope();suite.directiveController = suite.element.controller('heavyLoad');};it('应该正确编译', function () {//给定var givenTemplate = '<div 重载></div>';//什么时候Suite.compileDirective(givenTemplate);//然后期望(suite.directiveScope.title).toBeDefined();期望(suite.directiveScope.items).toBeDefined();期望(suite.heavyLoad.getHeavyString).toHaveBeenCalled();期望(suite.heavyLoad.getHeavyList).toHaveBeenCalled();});});

有两件事需要清理:

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

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

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

干杯!

as you may already know many of us who have a large quantity of written unit test has met with this not trivially solvable problem. I have around 3500+ unit tests written in the Jasmine syntax following the AngularJs unit testing guide. The tests are executed with Karma runner.

The problem is that they cannot be executed all at once due to some memory leaks. While running them the memory builds up no matter on what browser they are runned on and at some point the browser crashes and disconnects. The best workaround that I am aware of by now which is used in the community that have this problem is spliting the tests in multiple runs and at the end get the correct coverage by merging the results from the single runs.

When I first met with this problem I had around 1000 tests. After trying with all the available browsers for running I have split the tests in multiple runs, however it turned out that this is not good workaround for a long time. Now the tests are executed in 14+ single runs that are runned in parallel to reduce the time for completition and still IMO this cannot permanently solve the problem but delay it a litle bit because of resources limitation (RAM, CPU) and annoying time consumption.

Someone can argue that I have memory leaks in my code for which I cannot guarantee even though I don't have any problems alike when running the application in the browser. That is why I have created an example project that will highlight this problem.

There for reproducing this problem I am creating an Angular service which is heavy in memory consumption like this:

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

After that I have a simple directive which uses this service to initialize many DOM elements:

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

And at the end I am dynamically registering 1000 test suites with the test definition for the directive which btw is written as suggested in the Angular unit testing guide.

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

To try the example just checkout the project from GitHub and before running karma start run:

$ npm install
$ bower install

I am looking forward to finding where the problem is and solving it finally.

Cheers

解决方案

The problem was in the forgotten clean-up that needs to be done after each test. After adding it the number of tests does not matter anymore because the memory consumption is stable and the tests can be run in any browser.

I have added a modification of the previous test definition here that shows the solution with successfully executing 3000 dinamically registered tests.

Here is how the test looks like now:

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

});

There are two things that need to be cleaned-up:

  • compiled element when using $compile for testing directives
  • all variables in the describe functions scope

The two of them are tricky and hard to find out and take into consideration. For the first one I already knew but it didn't helped much until I've discovered the second which is related with how Jasmine works inside. I have created an issue on their GitHub repository which should help finding better solution or at least spread this information among developers faster.

I hope that this answer will be helpful for lot of people having this problem. I will write some info too after I finish refactoring all my other tests.

Cheers!

这篇关于AngularJs 单元测试内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆