Les développeurs conviennent tous que les tests unitaires sont très bénéfiques dans les projets de développement. Ils vous aident à garantir la qualité de votre code, garantissant ainsi un développement plus stable et une plus grande confiance même lorsqu'une refactorisation est nécessaire.
Organigramme de développement piloté par les tests
L'affirmation du code d'AngularJS selon laquelle une testabilité plus élevée est en effet raisonnable. Seuls les exemples de tests de bout en bout répertoriés dans le document peuvent illustrer cela. Pour des projets comme AngularJS, même si les tests unitaires sont considérés comme faciles, il n'est pas facile de bien les faire. Même si la documentation officielle fournit des exemples détaillés, cela reste très difficile dans mon application actuelle. Ici, je vais simplement démontrer comment je le fais fonctionner.
Karma instantané
Karma est un framework d'exécution de tests développé par l'équipe Angular pour JavaScript. Il automatise facilement les tâches de test et remplace les opérations manuelles fastidieuses (telles que les ensembles de tests de régression ou le chargement des dépendances de tests cibles). La collaboration entre Karma et Angular est comme du beurre de cacahuète et de la gelée.
.Il vous suffit de définir le fichier de configuration dans Karma pour le démarrer, puis il exécutera automatiquement les cas de test dans l'environnement de test attendu. Vous pouvez spécifier l'environnement de test approprié dans le fichier de configuration. angular-seed est une solution que je recommande vivement et qui peut être mise en œuvre rapidement. La configuration de Karma dans mon récent projet est la suivante :
module.exports = function(config) { config.set({ basePath: '../', files: [ 'app/lib/angular/angular.js', 'app/lib/angular/angular-*.js', 'app/js/**/*.js', 'test/lib/recaptcha/recaptcha_ajax.js', 'test/lib/angular/angular-mocks.js', 'test/unit/**/*.js' ], exclude: [ 'app/lib/angular/angular-loader.js', 'app/lib/angular/*.min.js', 'app/lib/angular/angular-scenario.js' ], autoWatch: true, frameworks: ['jasmine'], browsers: ['PhantomJS'], plugins: [ 'karma-junit-reporter', 'karma-chrome-launcher', 'karma-firefox-launcher', 'karma-jasmine', 'karma-phantomjs-launcher' ], junitReporter: { outputFile: 'test_out/unit.xml', suite: 'unit' } }) }
Ceci est similaire à la configuration par défaut d'angular-seed mais présente les différences suivantes :
autoWatch est un paramètre vraiment sympa, il permettra à Karma de revenir automatiquement à vos cas de test en cas de modifications de fichiers. Vous pouvez installer Karma comme ceci :
npm install karma
angular-seed fournit un script simple inscripts/test.sh pour déclencher les tests Karma.
Concevoir des cas de test avec Jasmine
La plupart des ressources sont déjà disponibles lors de la conception de cas de tests unitaires pour Angular à l'aide de Jasmine - un framework de test JavaScript avec un modèle de développement basé sur le comportement.
C'est de cela dont je veux parler ensuite.
Si vous souhaitez tester unitairement le contrôleur AngularJS, vous pouvez utiliser l'injection de dépendances d'Angular injection de dépendances La fonction importe la version du service requise par le contrôleur dans le scénario de test et vérifie également si les résultats attendus sont corrects . Par exemple, j'ai défini ce contrôleur pour mettre en évidence l'onglet vers lequel il faut naviguer :
app.controller('NavCtrl', function($scope, $location) { $scope.isActive = function(route) { return route === $location.path(); }; })
Que ferais-je si je voulais tester la méthode isActive ? Je vais vérifier si la variable $locationservice renvoie la valeur attendue et si la méthode renvoie la valeur attendue. Par conséquent, dans notre description de test, nous définirons des variables locales pour sauvegarder la version contrôlée requise pendant le test et l'injecter dans le contrôleur correspondant en cas de besoin. Ensuite, dans le cas de test réel, nous ajouterons des assertions pour vérifier si les résultats réels sont corrects. L'ensemble du processus est le suivant :
describe('NavCtrl', function() { var $scope, $location, $rootScope, createController; beforeEach(inject(function($injector) { $location = $injector.get('$location'); $rootScope = $injector.get('$rootScope'); $scope = $rootScope.$new(); var $controller = $injector.get('$controller'); createController = function() { return $controller('NavCtrl', { '$scope': $scope }); }; })); it('should have a method to check if the path is active', function() { var controller = createController(); $location.path('/about'); expect($location.path()).toBe('/about'); expect($scope.isActive('/about')).toBe(true); expect($scope.isActive('/contact')).toBe(false); }); });
En utilisant l'ensemble de la structure de base, vous pouvez concevoir différents types de tests. Étant donné que notre scénario de test utilise l'environnement local pour appeler le contrôleur, vous pouvez également ajouter des attributs supplémentaires, puis exécuter une méthode pour effacer ces attributs, puis vérifier si les attributs ont été effacés.
$httpBackendC'est cool
Et si vous appelez $httpservice pour demander ou envoyer des données au serveur ? Heureusement, Angular fournit un
Méthode simulée de $httpBackend. De cette façon, vous pouvez personnaliser le contenu de la réponse du serveur ou vous assurer que les résultats de la réponse du serveur sont cohérents avec les attentes du test unitaire.
Les détails spécifiques sont les suivants :
describe('MainCtrl', function() { var $scope, $rootScope, $httpBackend, $timeout, createController; beforeEach(inject(function($injector) { $timeout = $injector.get('$timeout'); $httpBackend = $injector.get('$httpBackend'); $rootScope = $injector.get('$rootScope'); $scope = $rootScope.$new(); var $controller = $injector.get('$controller'); createController = function() { return $controller('MainCtrl', { '$scope': $scope }); }; })); afterEach(function() { $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); it('should run the Test to get the link data from the go backend', function() { var controller = createController(); $scope.urlToScrape = 'success.com'; $httpBackend.expect('GET', '/slurp?urlToScrape=http:%2F%2Fsuccess.com') .respond({ "success": true, "links": ["http://www.google.com", "http://angularjs.org", "http://amazon.com"] }); // have to use $apply to trigger the $digest which will // take care of the HTTP request $scope.$apply(function() { $scope.runTest(); }); expect($scope.parseOriginalUrlStatus).toEqual('calling'); $httpBackend.flush(); expect($scope.retrievedUrls).toEqual(["http://www.google.com", "http://angularjs.org", "http://amazon.com"]); expect($scope.parseOriginalUrlStatus).toEqual('waiting'); expect($scope.doneScrapingOriginalUrl).toEqual(true); }); });
Comme vous pouvez le voir, l'appel beforeEach est en fait très similaire. La seule différence est que nous obtenons $httpBackend de l'injecteur au lieu de l'obtenir directement. Néanmoins, il existe des différences évidentes lors de la création de différents tests. Pour commencer, il y aura une méthode afterEachcall pour garantir que $httpBackend n'aura pas de requêtes anormales évidentes après chaque exécution de cas d'utilisation. Si vous examinez les paramètres du scénario de test et l'application de la méthode $httpBackend, vous constaterez que certaines choses ne sont pas si intuitives.
En fait, la méthode d'appel de $httpBackend est simple et claire, mais ce n'est pas suffisant - nous devons encapsuler l'appel dans la méthode $scope.runTest dans le test réel dans la méthode de transmission de la valeur à $scope .$appliquer. De cette façon, la requête HTTP ne peut être traitée qu'après le déclenchement de $digest. Comme vous pouvez le voir, $httpBackend ne sera pas analysé tant que nous n'aurons pas appelé la méthode $httpBackend.flush(), qui garantit que nous pouvons vérifier si le résultat renvoyé est correct lors de l'appel (dans l'exemple ci-dessus, le $scope du contrôleur. La propriété parseOriginalUrlStatusproperty sera transmise à l'appelant, afin que nous puissions la surveiller en temps réel)
Les prochaines lignes de code sont des assertions qui détectent l'attribut $scopethat lors de l'appel. Cool, non ?
Astuce : dans certains tests unitaires, les utilisateurs sont habitués à marquer les étendues sans $ comme variables. Ce n'est pas obligatoire ni exagéré dans la documentation Angular, mais j'utilise $scopelike afin d'améliorer la lisibilité et la cohérence.
Conclusion
C'est peut-être une de ces choses qui me viennent naturellement, mais apprendre à écrire des tests unitaires en Angular a été vraiment assez pénible pour moi au début. J'ai découvert qu'une grande partie de ma compréhension de la façon de démarrer provenait d'un patchwork de divers articles de blog et de ressources sur Internet, sans meilleures pratiques vraiment cohérentes ou claires, mais plutôt grâce à des choix aléatoires qui se sont imposés naturellement. Je voulais fournir une documentation sur ce que j'ai obtenu pour aider ceux qui pourraient avoir des difficultés parce qu'ils veulent simplement écrire du code plutôt que d'avoir à comprendre toutes les fonctionnalités étranges et uniques de l'utilisation d'Angular et de Jasmine. J’espère donc que cet article pourra vous être utile.