2015-10-07 12 views
22

come forse già sapete molti di noi che hanno una grande quantità di test di unità scritte hanno incontrato questo problema non banalmente risolvibile. Ho circa 3500+ test unitari scritti nella sintassi Jasmine seguendo la guida di AngularJs unit testing. I test sono eseguiti con il corridore Karma.L'unità AngularJs verifica perdite di memoria

Il problema è che non possono essere eseguiti tutti in una volta a causa di alcune perdite di memoria. Durante l'esecuzione, la memoria si accumula indipendentemente dal browser su cui vengono eseguiti e ad un certo punto il browser si blocca e si disconnette. Il miglior trucco di cui sono a conoscenza ormai che è usato nella comunità che ha questo problema è quello di dividere i test in più esecuzioni e alla fine ottenere la copertura corretta unendo i risultati delle singole esecuzioni.

Quando ho incontrato per la prima volta questo problema, ho avuto circa 1000 test. Dopo aver provato con tutti i browser disponibili per l'esecuzione, ho suddiviso i test in più esecuzioni, tuttavia è risultato che non è una buona soluzione per un lungo periodo di tempo. Ora i test vengono eseguiti in più di 14 sessioni singole che vengono eseguite in parallelo per ridurre il tempo di completamento e ancora IMO non è in grado di risolvere il problema in modo definitivo, ma di ritardarlo a causa della limitazione delle risorse (RAM, CPU) e del fastidioso consumo di tempo.

Qualcuno può obiettare che ho perdite di memoria nel mio codice per le quali non posso garantire anche se non ho alcun problema allo stesso modo durante l'esecuzione dell'applicazione nel browser. Ecco perché ho creato un progetto di esempio che metterà in evidenza questo problema.

C'è per la riproduzione di questo problema che sto creando un angolare service che è pesante nel consumo di memoria in questo modo:

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

Dopo che ho un semplice directive che utilizza questo servizio per inizializzare molti elementi 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()); 
    } 
    }; 
}); 

E alla fine sto registrando in modo dinamico 1000 suite di test con la test definition per la direttiva, che btw è scritto come suggerito nella guida unit testing angolare.

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

Per provare l'esempio appena checkout il progetto da GitHub e prima di eseguire karma iniziare run:

$ npm install 
$ bower install 

Non vedo l'ora di trovare dove è il problema e risolvere finalmente.

Acclamazioni

risposta

19

Il problema era nella pulizia dimenticato che deve essere fatto dopo ogni prova. Dopo averlo aggiunto, il numero di test non ha più importanza perché il consumo di memoria è stabile e i test possono essere eseguiti in qualsiasi browser.

Ho aggiunto una modifica alla precedente definizione di prova here che mostra la soluzione con l'esecuzione corretta di 3000 test registrati dinamicamente.

Ecco come il test si presenta come la società:

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

}); 

ci sono due cose che hanno bisogno di essere puliti-up:

  • elemento compilata quando si utilizza $ compilare per direttive di test
  • tutte le variabili nelle funzioni descrittive scope

I due sono tric ky e difficile da scoprire e prendere in considerazione. Per il primo lo sapevo già, ma non mi ha aiutato molto fino a quando non ho scoperto il secondo che è relativo a come Jasmine lavora all'interno. Ho creato un issue nel loro repository GitHub che dovrebbe aiutare a trovare una soluzione migliore o almeno a diffondere queste informazioni tra gli sviluppatori più velocemente.

Spero che questa risposta sia utile per molte persone che hanno questo problema. Scriverò alcune informazioni anche dopo aver finito di refactoring tutti i miei altri test.

Cheers!

+1

Dopo aver eseguito correttamente i risultati del refactoring sono stati eseguiti circa 4000 test in ~ 2min. –

+0

Come sei riuscito a farlo? Ho impostato i test precedenti per usare "this" per la pulizia di Jasmine, ma dopo aver eseguito 600 test il runner fallisce su PhantomJS. La stessa cosa si verifica ma con meno test eseguiti su Chrome. –

+2

Inoltre, a partire da 1.5.1 puoi usare 'beforeAll' invece di' beforeEach' per deridere il modulo una volta per descrizione, dovrebbe migliorare le prestazioni ancora di più. https://docs.angularjs.org/guide/unit-testing#using-beforeall- – vivascau