Utiliser istanbul pour voir le code coverage de tests lancés avec protractor
Posted on 2015-09-25 in Programmation
Récemment j'ai eu besoin d'avoir du code coverage (assuré par istanbul) pour des tests d'intégration d'une application AngularJS. Ces tests sont lancés avec protractor et ce n'est pas aussi simple qu'il n'y parait. L'idée de base est :
- D'associer une fonction à la clé onPrepare dans la configuration de protractor. Dans cette fonction on définit un rapporteur qui va pour chaque test collecter le code coverage.
- D'utiliser un plugin (l'idée initiale est de Jeffrey Barrus) afin d'attendre que le rapport soit écrit (via une promise). En effet, protractor dispose d'une option onComplete qui est une fonction appelée lorsque les tests sont terminés. Cependant, cette fonction ne supporte pas l'asynchronisme : elle ne supporte pas les callbacks et ne va pas attendre qu'une promise soit résolue. Par conséquent, il est possible que le processus de protractor se termine avant que le rapport ne soit écrit ou que istanbul n'ait reçu les données de coverage. Heureusement, la méthode tearDown d'un plugin peut retourner une promise et protractor attendra sa résolution avant de quitter.
Je suppose que vous savez comment préparer vos fichiers javascript pour istanbul (étape appelée instrumentation). Si ce n'est pas le cas, vous pouvez lire la doc.
Les configurations sont les suivantes (disponibles en téléchargement en fin d'article) :
1 var istanbul = require('istanbul'); 2 var collector = new istanbul.Collector(); 3 var reporter; 4 var waitPlugin = require('./waitPlugin'); 5 6 function report() { 7 if (reporter) { 8 reporter.add('json'); 9 reporter.write(collector, true, function () { 10 console.log('Coverage report successfully written'); 11 }); 12 } 13 } 14 15 exports.config = { 16 specs: [ 17 '../test/integration/*.spec.js', 18 '../test/selenium/*_test.js' 19 ], 20 seleniumAddress: '${seleniumAddress}', 21 maxSessions: 1, 22 multiCapabilities: [ 23 {browserName: 'firefox', shardTestFiles: true, maxInstances: 1} 24 ], 25 framework: 'jasmine2', 26 plugins: [{path: './waitPlugin.js'}], 27 onPrepare: function () { 28 var jasmineEnv = jasmine.getEnv(); 29 waitPlugin.setOnComplete(report); 30 browser.driver.manage().window().maximize(); 31 browser.get('${testPortalAddress}'); 32 33 jasmineEnv.addReporter(new function () { 34 this.specDone = function (spec) { 35 if (spec.status !== 'failed') { 36 var name = spec.fullName.replace(/ /g, '_'); 37 var reportfile = 'coverage/integration/json/' + name; 38 reporter = new istanbul.Reporter(undefined, reportfile); 39 var promise = browser.driver.executeScript('return __coverage__;') 40 .then(function (coverageResults) { 41 collector.add(coverageResults); 42 }); 43 waitPlugin.waitList.push(promise); 44 } 45 }; 46 }); 47 } 48 };
- On commence par charger les modules (istanbul, le collecteur, le plugin). On crée également la variable reporter. Elle contiendra par la suite un objet utilisé dans différentes fonctions.
- Ensuite on crée la fonction report qui sera utilisé par la suite pour écrire le rapport. Pour cela, on donne le format du rapport (ici json). Plusieurs formats sont supportés et istanbul peut générer un rapport dans plusieurs formats à la fois.
- Dans la configuration de protractor, rien de particulier jusqu'à la ligne 27
pour la fonction onPrepare.
- À la ligne 29, on définit la fonction à appeler une fois que toutes les promises du plugin « attendre » sont résolues, en l'occurrence notre fonction report.
- À la ligne 33, on ajoute un rapporteur à jasmine, le framework de tests. Ce rapporteur comprend une méthode specDone qui est appelée (comme son nom l'indique) à chaque fois qu'un spec est terminé. Si les tests ont réussi, on récupère le code coverage depuis la variable globale __coverage__ de istanbul et on ajoute le résultat au collecteur. Ces opérations étant asynchrone et renvoyant une promise, on ajoute cette dernière à la liste de promises à attendre dans notre plugin.
Pour le plugin wait, rien de très compliqué, je pense que le code parle de lui même. Pensez simplement à installer la bibliothèque q dont dépend le script avec npm install q ou npm install --save q pour ajouter q à votre package.json. En cas de problèmes, vous pouvez laisser un commentaire.
1 var q = require('q'); 2 3 var waitList = []; 4 var onComplete; 5 6 exports.waitList = waitList; 7 8 exports.setOnComplete = function(cb) { 9 onComplete = cb; 10 }; 11 12 exports.teardown = function () { 13 return q.all(waitList) 14 .then(function() { 15 if (onComplete) { 16 return onComplete(); 17 } 18 }); 19 };
Si comme moi, vous devez lancer chaque fichier spec dans un nouveau navigateur, pour avoir la couverture globale, vous devez écrire le résultat de chaque jeu de tests dans un fichier json puis rassembler les résultats (par exemple en un rapport html plus lisible) avec la commande suivante :
istanbul report --include 'coverage/integration/json/**/*.json' --dir 'coverage/integration' html
Si en revanche vous pouvez lancer tous les tests dans un seul navigateur, vous pouvez récupérer le rapport au format HTML directement en changeant ligne 8 de la configuration de protractor json en html.