2015-10-07 15 views
22

, ponieważ być może już wiesz, że wielu z nas, którzy mają dużą liczbę pisemnych testów jednostkowych, spotkało się z tym niełatwym do rozwiązania problemem. Mam ponad 3500 testów jednostkowych zapisanych w składni Jasmine po przewodniku AngularJs unit testing. Testy są wykonywane z biegaczem Karma.Urządzenie AngularJs testuje wycieki pamięci

Problem polega na tym, że nie można ich wykonać na raz ze względu na pewne wycieki pamięci. Podczas ich działania gromadzona jest pamięć niezależnie od tego, w jakiej przeglądarce są uruchamiane iw pewnym momencie przeglądarka ulega awarii i rozłącza się. Najlepszym sposobem obejścia tego problemu, który jest obecnie stosowany w społeczności, która ma ten problem, jest dzielenie testów na wiele przebiegów i na końcu uzyskiwanie poprawnego pokrycia przez scalanie wyników z pojedynczych przebiegów.

Kiedy po raz pierwszy spotkałem się z tym problemem, miałem około 1000 testów. Po przetestowaniu wszystkich dostępnych przeglądarek pod kątem dzielenia testów w wielu seriach okazało się, że nie jest to dobre rozwiązanie przez długi czas. Teraz testy są wykonywane w 14+ pojedynczych przebiegach, które są uruchamiane równolegle w celu skrócenia czasu na ukończenie, a mimo to IMO nie może trwale rozwiązać problemu, ale opóźnia go o trochę więcej ze względu na ograniczenia zasobów (RAM, CPU) i denerwujące zużycie czasu.

Ktoś może twierdzić, że mam wycieki pamięci w moim kodzie, co do których nie mogę zagwarantować, mimo że nie mam żadnych problemów zarówno podczas uruchamiania aplikacji w przeglądarce. Właśnie dlatego stworzyłem przykładowy projekt, który podkreśli ten problem.

Nie do odtworzenia tego problemu tworzę kątowym service który jest ciężki zużycia pamięci, takich jak ten:

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

Po że mam proste directive który korzysta z tej usługi w celu zainicjowania wielu elementów 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()); 
    } 
    }; 
}); 

I na koniec dynamicznie rejestruję 1000 zestawów testów z test definition dla dyrektywy, która jest napisana zgodnie z sugestią w przewodniku Angular unit testing.

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

spróbować przykład tylko kasa projekt z GitHub i przed uruchomieniem karmę rozpocząć Run:

$ npm install 
$ bower install 

niecierpliwością czekam do znalezienia gdzie jest problem i rozwiązać go w końcu.

Cheers

Odpowiedz

19

Problem był w zapomnianej sprzątania, które należy wykonać po każdym teście. Po dodaniu liczby testów nie ma już znaczenia, ponieważ zużycie pamięci jest stabilne, a testy można uruchomić w dowolnej przeglądarce.

Dodałem modyfikację poprzedniej definicji testu here, która pokazuje rozwiązanie z pomyślnym wykonaniem 3000 testów zarejestrowanych dinamicznie.

Oto jak test wygląda teraz:

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

}); 

Są dwie rzeczy, które muszą być oczyszczone-up:

  • skompilowany elementem przy użyciu $ skompilować dyrektyw testowania
  • wszystkie zmienne w opisanym zakresie funkcji:

Dwa z nich to triki ky i trudno je znaleźć i wziąć pod uwagę. Po pierwsze, już wiedziałem, ale niewiele mi to pomogło, dopóki nie odkryłem drugiej, która jest związana z tym, jak Jasmine pracuje w środku. Mam utworzony issue na ich repozytorium GitHub, który powinien pomóc znaleźć lepsze rozwiązanie lub przynajmniej rozpowszechniać te informacje wśród programistów szybciej.

Mam nadzieję, że ta odpowiedź będzie pomocna dla wielu osób mających ten problem. Napiszę też trochę informacji po zakończeniu refaktoryzacji wszystkich innych moich testów.

Pozdrawiam!

+1

Po pomyślnym zakończeniu refaktoryzacji wyniki ~ 4000 testów wykonano w ~ 2min. –

+0

Jak mogłeś to zrobić? Przygotowałem poprzednie testy do uruchomienia przy użyciu 'tego' do czyszczenia przez Jasmine, ale po uruchomieniu 600 testów biegacz po prostu zawiedzie w PhantomJS. To samo ma miejsce, ale w Chrome uruchomiono mniej testów. –

+2

Ponadto, począwszy od wersji 1.5.1 można użyć 'beforeAll' zamiast' beforeEach', aby kpić z modułu raz na opis, powinno to jeszcze bardziej poprawić wydajność. https://docs.angularjs.org/guide/unit-testing#using-beforeall- – vivascau

Powiązane problemy