Créer un navigateur personnalisé - 10ème partie

Couverture de code avec istanbul et intégration continue avec coveralls.io

10 mai 2016

Introduction

Ce projet est open source et accessible depuis GitHub GL-Browser.

Cet article est le 10ème d'une série d'articles concernant la création d'un navigateur internet incluant par défaut des fonctionnalités que l'on retrouve habituellement sous forme d'extensions dans Chrome ou Firefox.

Exemples de fonctionnalités :

Nous souhaitons :

Nous utilisons Electron qui mixe le navigateur open source Chromium avec la richesse du framework Node.js.

Objectifs

La couverture de code est le taux de code source testé.

Dans ce 10ème article, nous automatiserons la génération d'un rapport de couverture du code source.

Ce rapport permet de connaître les parties du code non couvertes par les tests.

Résumé des articles précédents

Article 1: initialisation du projet avec Electron.
Article 2: injection du CSS et du JavaScript dans la page de Google.
Article 3: injection de scripts JavaScript et de styles CSS suivant l'URL.
Article 4: bloquer les requêtes de suivi/tracking des utilisateurs.
Article 5: amélioration de l'interface utilisateur avec Bootstrap.
Article 6: encapsulation des composants visuels avec Riot.
Article 7: intégration d'un gestionnaire de mots de passe.
Article 8: intégration d'un gestionnaire de favoris.
Article 9: Automatisation des tests avec Mocha, vérification syntaxique avec ESLint et intégration continue avec Travis CI.

Pour récupérer directement les sources du 10ème article :

git clone https://github.com/emmanuelroecker/GL-Browser
git checkout article10

Couverture de code

Plusieurs opérations successives doivent être effectuées pour connaître la couverture de code :

  1. Instrumentaliser les codes sources
    L'instrumentation de code permet d'enregistrer les instructions exécutées et les chemins empruntés lors de l'exécution.
  2. Exécuter les tests sur les codes sources instrumentalisés
    Le résultat des tests est enregistré dans un fichier
  3. Générer un rapport de couverture
    Un rapport de couverture est généré à partir du résultat des tests
  4. Envoyer le rapport de couverture au site internet coveralls.io
    La couverture de code peut être analysée par internet.

Précompiler les composants Riot

Pour pouvoir être instrumentalisés, les codes sources des composants Riot doivent être précompilés.
Dans les précédents articles, les composants Riot étaient compilés à la volée à l'exécution du navigateur.

Une nouvelle commande compile est ajoutée à package.json :

{
    "scripts": {
        "compile": "riot ./riot ./js/riot"
    }
}

Pour compiler les composants Riot :

npm run compile

Tous les composants du répertoire riot/ sont compilés en JavaScript dans le répertoire js/riot/.

Par exemple, riot/autologin.tag devient js/riot/autologin.js.

Installer istanbul

istanbul est un outil libre qui permet d'instrumentaliser le code source :

npm install --save-dev istanbul

Une fois installé, le fichier package.json contient :

{
    "dependencies": {
        "app-module-path": "^1.0.6",
        "bootstrap": "^3.3.0",
        "commander": "^2.9.0",
        "electron-prebuilt": "^0.37.0",
        "jquery": "^1.9.1",
        "js-yaml": "^3.5.0",
        "match-pattern": "^0.0.1",
        "riot": "^2.3.0"
    },
    "devDependencies": {
        "electron-mocha": "^1.2.1",
        "eslint": "^2.8.0",
        "istanbul": "^0.4.3"
    }
}

Instrumentaliser le code source

L'instrumentation de code permet d'enregistrer les instructions exécutées et les chemins empruntés lors de l'exécution.

La commande cover utilisant istanbul est ajoutée à package.json :

{
    "scripts": {
        "compile": "riot ./riot ./js/riot",
        "cover": "rm -rf js-cov/* && istanbul instrument -o js-cov js"
    }
}

Pour instrumentaliser les codes sources :

npm run cover

le répertoire js/ contient les fichiers sources JavaScript originaux.
le répertoire js-cov/ contient les fichiers sources JavaScript instrumentalisés.

Par exemple, le code js/riot/raw.js :

(function(tagger) {
  if (typeof define === 'function' && define.amd) {
    define(function(require, exports, module) { tagger(require('riot'), require, exports, module)})
  } else if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
    tagger(require('riot'), require, exports, module)
  } else {
    tagger(window.riot)
  }
})(function(riot, require, exports, module) {

riot.tag2('raw', '<span></span>', '', '', function(opts) {
    this.root.innerHTML = opts.content
});
});

une fois instrumentalisé devient le fichier js-cov/riot/raw.js :

var __cov_vJxK49R6ULDmpD4mLRqYYg = (Function('return this'))();
if (!__cov_vJxK49R6ULDmpD4mLRqYYg.__coverage__) { __cov_vJxK49R6ULDmpD4mLRqYYg.__coverage__ = {}; }
__cov_vJxK49R6ULDmpD4mLRqYYg = __cov_vJxK49R6ULDmpD4mLRqYYg.__coverage__;
if (!(__cov_vJxK49R6ULDmpD4mLRqYYg['C:\\glicer\\App\\lib\\GL-Browser\\js\\riot\\raw.js'])) {
   __cov_vJxK49R6ULDmpD4mLRqYYg['C:\\glicer\\App\\lib\\GL-Browser\\js\\riot\\raw.js'] = {"path":"C:\\glicer\\App\\lib\\GL-Browser\\js\\riot\\raw.js","s":{"1":0,"2":0,"3":0,"4":0,"5":0,"6":0,"7":0,"8":0,"9":0},"b":{"1":[0,0],"2":[0,0],"3":[0,0],"4":[0,0]},"f":{"1":0,"2":0,"3":0,"4":0},"fnMap":{"1":{"name":"(anonymous_1)","line":2,"loc":{"start":{"line":2,"column":1},"end":{"line":2,"column":18}}},"2":{"name":"(anonymous_2)","line":4,"loc":{"start":{"line":4,"column":11},"end":{"line":4,"column":46}}},"3":{"name":"(anonymous_3)","line":10,"loc":{"start":{"line":10,"column":3},"end":{"line":10,"column":44}}},"4":{"name":"(anonymous_4)","line":12,"loc":{"start":{"line":12,"column":42},"end":{"line":12,"column":57}}}},"statementMap":{"1":{"start":{"line":2,"column":0},"end":{"line":15,"column":3}},"2":{"start":{"line":3,"column":2},"end":{"line":9,"column":3}},"3":{"start":{"line":4,"column":4},"end":{"line":4,"column":99}},"4":{"start":{"line":4,"column":48},"end":{"line":4,"column":97}},"5":{"start":{"line":5,"column":9},"end":{"line":9,"column":3}},"6":{"start":{"line":6,"column":4},"end":{"line":6,"column":53}},"7":{"start":{"line":8,"column":4},"end":{"line":8,"column":23}},"8":{"start":{"line":12,"column":0},"end":{"line":14,"column":3}},"9":{"start":{"line":13,"column":4},"end":{"line":13,"column":38}}},"branchMap":{"1":{"line":3,"type":"if","locations":[{"start":{"line":3,"column":2},"end":{"line":3,"column":2}},{"start":{"line":3,"column":2},"end":{"line":3,"column":2}}]},"2":{"line":3,"type":"binary-expr","locations":[{"start":{"line":3,"column":6},"end":{"line":3,"column":34}},{"start":{"line":3,"column":38},"end":{"line":3,"column":48}}]},"3":{"line":5,"type":"if","locations":[{"start":{"line":5,"column":9},"end":{"line":5,"column":9}},{"start":{"line":5,"column":9},"end":{"line":5,"column":9}}]},"4":{"line":5,"type":"binary-expr","locations":[{"start":{"line":5,"column":13},"end":{"line":5,"column":42}},{"start":{"line":5,"column":46},"end":{"line":5,"column":83}}]}}};
}
__cov_vJxK49R6ULDmpD4mLRqYYg = __cov_vJxK49R6ULDmpD4mLRqYYg['C:\\glicer\\App\\lib\\GL-Browser\\js\\riot\\raw.js'];
__cov_vJxK49R6ULDmpD4mLRqYYg.s['1']++;(function(tagger){__cov_vJxK49R6ULDmpD4mLRqYYg.f['1']++;__cov_vJxK49R6ULDmpD4mLRqYYg.s['2']++;if((__cov_vJxK49R6ULDmpD4mLRqYYg.b['2'][0]++,typeof define==='function')&&(__cov_vJxK49R6ULDmpD4mLRqYYg.b['2'][1]++,define.amd)){__cov_vJxK49R6ULDmpD4mLRqYYg.b['1'][0]++;__cov_vJxK49R6ULDmpD4mLRqYYg.s['3']++;define(function(require,exports,module){__cov_vJxK49R6ULDmpD4mLRqYYg.f['2']++;__cov_vJxK49R6ULDmpD4mLRqYYg.s['4']++;tagger(require('riot'),require,exports,module);});}else{__cov_vJxK49R6ULDmpD4mLRqYYg.b['1'][1]++;__cov_vJxK49R6ULDmpD4mLRqYYg.s['5']++;if((__cov_vJxK49R6ULDmpD4mLRqYYg.b['4'][0]++,typeof module!=='undefined')&&(__cov_vJxK49R6ULDmpD4mLRqYYg.b['4'][1]++,typeof module.exports!=='undefined')){__cov_vJxK49R6ULDmpD4mLRqYYg.b['3'][0]++;__cov_vJxK49R6ULDmpD4mLRqYYg.s['6']++;tagger(require('riot'),require,exports,module);}else{__cov_vJxK49R6ULDmpD4mLRqYYg.b['3'][1]++;__cov_vJxK49R6ULDmpD4mLRqYYg.s['7']++;tagger(window.riot);}}}(function(riot,require,exports,module){__cov_vJxK49R6ULDmpD4mLRqYYg.f['3']++;__cov_vJxK49R6ULDmpD4mLRqYYg.s['8']++;riot.tag2('raw','<span></span>','','',function(opts){__cov_vJxK49R6ULDmpD4mLRqYYg.f['4']++;__cov_vJxK49R6ULDmpD4mLRqYYg.s['9']++;this.root.innerHTML=opts.content;});}));

Génération d'un rapport des tests effectués sur le code instrumentalisé

La commande mocha-cov est ajoutée au fichier package.json.
mocha-cov exécute les tests sur les codes sources instrumentalisés du répertoire js-cov/ généré par la commande cover.

2 options sont indiquées à electron-mocha :

{
  "scripts": {
    "compile": "riot ./riot ./js/riot",
    "cover": "rm -rf js-cov/* && istanbul instrument -o js-cov js",
    "mocha-cov": "electron-mocha --renderer --recursive ./tests/js ./tests/riot --require ./tests/setup-cov.js --reporter tests/coverage",
    "mocha": "electron-mocha --renderer --recursive ./tests/js ./tests/riot --require ./tests/setup.js"
  }
}

Pour exécuter les tests sur le code instrumentalisé :

npm run mocha-cov

Le fichier de rapport coverage/coverage-final.json est automatiquement créé.

Pour exécuter les tests sans couverture de code :

npm run mocha

Création du rapport de couverture

istanbul permet de convertir le fichier de rapport coverage/coverage-final.json créé par la commande mocha-cov en un fichier interprétable par coveralls.io.

La commande report-cov est ajoutée à package.json :

{
  "scripts": {
    "compile": "riot ./riot ./js/riot",
    "cover": "rm -rf js-cov/* && istanbul instrument -o js-cov js",
    "mocha-cov": "electron-mocha --renderer --recursive ./tests/js ./tests/riot --require ./tests/setup-cov.js --reporter tests/coverage",
    "mocha": "electron-mocha --renderer --recursive ./tests/js ./tests/riot --require ./tests/setup.js",
    "report-cov": "istanbul report lcovonly"
  }
}

Pour générer le rapport de couverture :

npm run report-cov

Le fichier coverage/lcov.info est automatiquement créé.

Exécution du cycle de couverture

La commande test-cov exécute successivement toutes les commandes nécessaires à la génération du rapport :

{
    "scripts": {
        "test": "npm run lint && npm run mocha",
        "test-cov": "npm run lint && npm run cover && npm run mocha-cov && npm run report-cov",
        "start": "electron app.js",
        "lint": "eslint .",
        "compile": "riot -m ./riot ./js/riot",
        "mocha-cov": "electron-mocha --renderer --recursive ./tests/js ./tests/riot --require ./tests/setup-cov.js --reporter tests/coverage",
        "mocha": "electron-mocha --renderer --recursive ./tests/js ./tests/riot --require ./tests/setup.js",
        "cover": "rm -rf js-cov/* && istanbul instrument -o js-cov js",
        "report-cov": "istanbul report lcovonly"
    }
}

Pour exécuter le cycle :

npm run test-cov

Intégration continue avec coveralls.io

Le site internet coveralls.io permet d'afficher les parties du code non couvertes par les tests.

L'utilisation de coveralls.io est gratuite pour les projets open source.

coveralls.io peut être interfacé avec Travis CI

Lors d'un git-push sur le dépôt hébergé par GitHub, Travis CI effectue automatiquement les opérations suivantes :

Installer la librairie coveralls

la librairie coveralls permet d'envoyer le rapport de couverture de code au site internet coveralls.io.

coveralls est ajouté à package.json :

npm install --save-dev coveralls

le fichier package.json contient désormais :

{
    "dependencies": {
        "app-module-path": "^1.0.6",
        "bootstrap": "^3.3.0",
        "commander": "^2.9.0",
        "electron-prebuilt": "^0.37.0",
        "jquery": "^1.9.1",
        "js-yaml": "^3.5.0",
        "match-pattern": "^0.0.1",
        "riot": "^2.3.0"
    },
    "devDependencies": {
        "coveralls": "^2.11.9",
        "electron-mocha": "^1.2.1",
        "eslint": "^2.8.0",
        "istanbul": "^0.4.3"
    }
}

Configurer Travis CI

Pour interfacer coveralls.io avec Travis CI, le script d'envoi automatique du rapport de couverture à coveralls.io est ajouté au fichier .travis.yml .

after_script:
    - cat ./coverage/lcov.info | coveralls; 

ainsi que l'exécution des tests :

script:
  - npm run test-cov

Travis CI affiche :

Travis

Configurer coveralls.io

Le dépôt du projet GitHub est activé depuis l'interface de coveralls.io :

Flick repository on coveralls

coveralls.io permet de visualiser le détail des couvertures par fichier :

Coveralls report

Le lien du badge de couverture est ajouté au fichier readme.md du projet :

[![Coverage Status](https://coveralls.io/repos/github/emmanuelroecker/GL-Browser/badge.svg?branch=master)](https://coveralls.io/github/emmanuelroecker/GL-Browser?branch=master)

Résultat

Build Passing Badge

Prochain article

Le prochain article traitera de la génération d'un installeur automatique du navigateur GL-Browser.