Création d'un flux de travail de test d'intégration continue à l'aide d'actions GitHub

Publié: 2022-03-10
Résumé rapide ↬ À l'aide de ce didacticiel, vous pouvez apprendre à créer un workflow d'intégration continue pour votre API Node JS REST en utilisant GitHub Actions ainsi qu'à signaler la couverture des tests avec Coveralls.

Lorsque vous contribuez à des projets sur des plates-formes de contrôle de version telles que GitHub et Bitbucket, la convention est qu'il existe la branche principale contenant la base de code fonctionnelle. Ensuite, il existe d'autres branches dans lesquelles plusieurs développeurs peuvent travailler sur des copies de la principale pour soit ajouter une nouvelle fonctionnalité, corriger un bogue, etc. Cela a beaucoup de sens car il devient plus facile de surveiller le type d'effet que les modifications entrantes auront sur le code existant. S'il y a une erreur, elle peut facilement être tracée et corrigée avant d'intégrer les modifications dans la branche principale. Il peut être fastidieux de parcourir manuellement chaque ligne de code à la recherche d'erreurs ou de bogues, même pour un petit projet. C'est là qu'intervient l'intégration continue.

Qu'est-ce que l'intégration continue (IC) ?

"L'intégration continue (CI) est la pratique consistant à automatiser l'intégration des modifications de code de plusieurs contributeurs dans un seul projet logiciel."

— Atlassian.com

L'idée générale derrière l'intégration continue (CI) est de s'assurer que les modifications apportées au projet ne "cassent pas la construction", c'est-à-dire ne ruinent pas la base de code existante. La mise en œuvre de l'intégration continue dans votre projet, selon la configuration de votre flux de travail, créerait une génération chaque fois que quelqu'un apporterait des modifications au référentiel.

Alors, qu'est-ce qu'une construction ?

Une construction - dans ce contexte - est la compilation du code source dans un format exécutable. Si cela réussit, cela signifie que les modifications entrantes n'auront pas d'impact négatif sur la base de code et qu'elles sont prêtes à partir. Cependant, si la construction échoue, les modifications devront être réévaluées. C'est pourquoi il est conseillé d'apporter des modifications à un projet en travaillant sur une copie du projet sur une branche différente avant de l'incorporer dans la base de code principale. De cette façon, si la construction échoue, il serait plus facile de déterminer d'où vient l'erreur, et cela n'affecte pas non plus votre code source principal.

« Plus tôt vous détectez les défauts, moins ils coûtent cher à réparer. »

— David Farley, Livraison continue : versions de logiciels fiables grâce à l'automatisation de la construction, des tests et du déploiement

Plusieurs outils sont disponibles pour vous aider à créer une intégration continue pour votre projet. Ceux-ci incluent Jenkins, TravisCI, CircleCI, GitLab CI, GitHub Actions, etc. Pour ce tutoriel, j'utiliserai GitHub Actions.

Actions GitHub pour l'intégration continue

CI Actions est une fonctionnalité relativement nouvelle sur GitHub et permet la création de flux de travail qui exécutent automatiquement la construction et les tests de votre projet. Un workflow contient un ou plusieurs travaux qui peuvent être activés lorsqu'un événement se produit. Cet événement peut être un push vers l'une des branches du référentiel ou la création d'une pull request. J'expliquerai ces termes en détail au fur et à mesure.

Commençons!

Conditions préalables

Il s'agit d'un didacticiel pour les débutants, je parlerai donc principalement de GitHub Actions CI au niveau de la surface. Les lecteurs doivent déjà être familiarisés avec la création d'une API Node JS REST à l'aide de la base de données PostgreSQL, Sequelize ORM et l'écriture de tests avec Mocha et Chai.

Vous devez également avoir les éléments suivants installés sur votre machine :

  • NodeJS,
  • PostgreSQL,
  • MNP,
  • VSCode (ou n'importe quel éditeur et terminal de votre choix).

Je vais utiliser une API REST que j'ai déjà créée et appelée countries-info-api . C'est une API simple sans autorisations basées sur les rôles (comme au moment de la rédaction de ce tutoriel). Cela signifie que n'importe qui peut ajouter, supprimer et/ou mettre à jour les détails d'un pays. Chaque pays aura un identifiant (UUID généré automatiquement), un nom, une capitale et une population. Pour y parvenir, j'ai utilisé Node js, express js framework et Postgresql pour la base de données.

Je vais expliquer brièvement comment j'ai configuré le serveur, la base de données avant de commencer à écrire les tests pour la couverture des tests et le fichier de workflow pour l'intégration continue.

Vous pouvez cloner le dépôt country countries-info-api pour suivre ou créer votre propre API.

Technologies utilisées : Node Js, NPM (un gestionnaire de paquets pour Javascript), base de données Postgresql, sequelize ORM, Babel.

Plus après saut! Continuez à lire ci-dessous ↓

Configuration du serveur

Avant de configurer le serveur, j'ai installé certaines dépendances de npm.

 npm install express dotenv cors npm install --save-dev @babel/core @babel/cli @babel/preset-env nodemon

J'utilise le framework express et j'écris au format ES6, j'aurai donc besoin de Babeljs pour compiler mon code. Vous pouvez lire la documentation officielle pour en savoir plus sur son fonctionnement et comment le configurer pour votre projet. Nodemon détectera toute modification apportée au code et redémarrera automatiquement le serveur.

Remarque : les packages Npm installés à l'aide de l' --save-dev ne sont requis que pendant les étapes de développement et sont visibles sous devDependencies dans le fichier package.json .

J'ai ajouté ce qui suit à mon fichier index.js :

 import express from "express"; import bodyParser from "body-parser"; import cors from "cors"; import "dotenv/config"; const app = express(); const port = process.env.PORT; app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); app.use(cors()); app.get("/", (req, res) => { res.send({message: "Welcome to the homepage!"}) }) app.listen(port, () => { console.log(`Server is running on ${port}...`) })

Cela configure notre API pour qu'elle s'exécute sur tout ce qui est attribué à la variable PORT dans le fichier .env . C'est également là que nous déclarerons des variables auxquelles nous ne voulons pas que d'autres aient facilement accès. Le dotenv npm charge nos variables d'environnement à partir de .env .

Maintenant, quand je lance npm run start dans mon terminal, j'obtiens ceci :

Serveur en cours d'exécution
Serveur opérationnel sur le port 3000. ( Grand aperçu )

Comme vous pouvez le voir, notre serveur est opérationnel. Yay!

Ce lien https://127.0.0.1:your_port_number/ dans votre navigateur Web devrait renvoyer le message de bienvenue. Autrement dit, tant que le serveur est en cours d'exécution.

Navigateur
Page d'accueil. ( Grand aperçu )

Ensuite, base de données et modèles.

J'ai créé le modèle de pays à l'aide de Sequelize et je me suis connecté à ma base de données Postgres. Sequelize est un ORM pour Nodejs. Un avantage majeur est qu'il nous fait gagner du temps lors de l'écriture de requêtes SQL brutes.

Puisque nous utilisons Postgresql, la base de données peut être créée via la ligne de commande psql en utilisant la commande CREATE DATABASE database_name . Cela peut également être fait sur votre terminal, mais je préfère PSQL Shell.

Dans le fichier env, nous allons configurer la chaîne de connexion de notre base de données, en suivant ce format ci-dessous.

 TEST_DATABASE_URL = postgres://<db_username>:<db_password>@127.0.0.1:5432/<database_name>

Pour mon modèle, j'ai suivi ce tutoriel de séquelle. Il est facile à suivre et explique tout sur la configuration de Sequelize.

Ensuite, je vais écrire des tests pour le modèle que je viens de créer et mettre en place la couverture sur Coverall.

Tests d'écriture et couverture des rapports

Pourquoi écrire des tests ? Personnellement, je pense que l'écriture de tests vous aide en tant que développeur à mieux comprendre comment votre logiciel est censé fonctionner entre les mains de votre utilisateur, car il s'agit d'un processus de brainstorming. Il vous aide également à découvrir les bugs à temps.

Essais :

Il existe différentes méthodes de test de logiciels, cependant, pour ce tutoriel, j'ai utilisé des tests unitaires et de bout en bout.

J'ai écrit mes tests en utilisant le framework de test Mocha et la bibliothèque d'assertions Chai. J'ai également installé sequelize-test-helpers pour aider à tester le modèle que j'ai créé à l'aide sequelize.define .

Couverture de test:

Il est conseillé de vérifier la couverture de vos tests car le résultat indique si nos cas de test couvrent réellement le code et également la quantité de code utilisée lorsque nous exécutons nos cas de test.

J'ai utilisé Istanbul (un outil de couverture de test), nyc (le client CLI d'Instabul) et Coveralls.

Selon la documentation, Istanbul instrumente votre code JavaScript ES5 et ES2015+ avec des compteurs de ligne, afin que vous puissiez suivre la façon dont vos tests unitaires exercent votre base de code.

Dans mon fichier package.json , le script de test exécute les tests et génère un rapport.

 { "scripts": { "test": "nyc --reporter=lcov --reporter=text mocha -r @babel/register ./src/test/index.js" } }

Au cours du processus, il créera un dossier .nyc_output contenant les informations de couverture brutes et un dossier de coverage contenant les fichiers de rapport de couverture. Les deux fichiers ne sont pas nécessaires sur mon référentiel, je les ai donc placés dans le fichier .gitignore .

Maintenant que nous avons généré un rapport, nous devons l'envoyer à Coveralls. Une chose intéressante à propos de Coveralls (et d'autres outils de couverture, je suppose) est la façon dont il rapporte votre couverture de test. La couverture est ventilée fichier par fichier et vous pouvez voir la couverture pertinente, les lignes couvertes et manquées, et ce qui a changé dans la couverture de construction.

Pour commencer, installez le package coveralls npm. Vous devez également vous connecter aux combinaisons et y ajouter le dépôt.

dépôt de combinaisons
Repo connecté aux combinaisons. ( Grand aperçu )

Configurez ensuite les combinaisons pour votre projet javascript en créant un fichier coveralls.yml dans votre répertoire racine. Ce fichier contiendra votre repo-token de dépôt obtenu à partir de la section des paramètres pour votre dépôt sur les combinaisons.

Un autre script nécessaire dans le fichier package.json est les scripts de couverture. Ce script sera utile lorsque nous créerons une version via Actions.

 { "scripts": { "coverage": "nyc npm run test && nyc report --reporter=text-lcov --reporter=lcov | node ./node_modules/coveralls/bin/coveralls.js --verbose" } }

Fondamentalement, il exécutera les tests, obtiendra le rapport et l'enverra aux combinaisons pour analyse.

Passons maintenant au point principal de ce tutoriel.

Créer un fichier de flux de travail Node JS

À ce stade, nous avons configuré les tâches nécessaires que nous exécuterons dans notre action GitHub. (Vous vous demandez ce que signifient les « emplois » ? Continuez à lire.)

GitHub a facilité la création du fichier de workflow en fournissant un modèle de démarrage. Comme indiqué sur la page Actions, il existe plusieurs modèles de flux de travail à des fins différentes. Pour ce tutoriel, nous utiliserons le workflow Node.js (que GitHub a déjà gentiment suggéré).

Page Actions
Page Actions GitHub. ( Grand aperçu )

Vous pouvez modifier le fichier directement sur GitHub mais je créerai manuellement le fichier sur mon dépôt local. Le dossier .github/workflows contenant le fichier node.js.yml sera dans le répertoire racine.

Ce fichier contient déjà quelques commandes de base et le premier commentaire explique ce qu'elles font.

 # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node

Je vais y apporter quelques modifications afin qu'en plus du commentaire ci-dessus, il gère également la couverture.

Mon fichier .node.js.yml :

 name: NodeJS CI on: ["push"] jobs: build: name: Build runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run build --if-present - run: npm run coverage - name: Coveralls uses: coverallsapp/github-action@master env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} COVERALLS_GIT_BRANCH: ${{ github.ref }} with: github-token: ${{ secrets.GITHUB_TOKEN }}

Qu'est-ce que ça veut dire?

Décomposons-le.

  • name
    Ce serait le nom de votre flux de travail (NodeJS CI) ou travail (build) et GitHub l'affichera sur la page d'actions de votre référentiel.
  • on
    Il s'agit de l'événement qui déclenche le workflow. Cette ligne dans mon fichier indique essentiellement à GitHub de déclencher le flux de travail chaque fois qu'une poussée est effectuée vers mon référentiel.
  • jobs
    Un flux de travail peut contenir au moins un ou plusieurs travaux et chaque travail s'exécute dans un environnement spécifié par runs-on . Dans l'exemple de fichier ci-dessus, il n'y a qu'un seul travail qui exécute la construction et exécute également la couverture, et il s'exécute dans un environnement Windows. Je peux également le séparer en deux tâches différentes comme celle-ci :

Mise à jour du fichier Node.yml

 name: NodeJS CI on: [push] jobs: build: name: Build runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v1 with: node-version: ${{ matrix.node-version }} - run: npm install - run: npm run build --if-present - run: npm run test coverage: name: Coveralls runs-on: windows-latest strategy: matrix: node-version: [12.x, 14.x] steps: - uses: coverallsapp/github-action@master env: COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} with: github-token: ${{ secrets.GITHUB_TOKEN }}
  • env
    Celui-ci contient les variables d'environnement qui sont disponibles pour tous les travaux et étapes spécifiques du flux de travail. Dans le travail de couverture, vous pouvez voir que les variables d'environnement ont été "masquées". Ils peuvent être trouvés dans la page des secrets de votre référentiel sous les paramètres.
  • steps
    Il s'agit essentiellement d'une liste des étapes à suivre lors de l'exécution de ce travail.
  • Le travail de build fait un certain nombre de choses :
    • Il utilise une action de paiement (v2 signifie la version) qui extrait littéralement votre référentiel afin qu'il soit accessible par votre flux de travail ;
    • Il utilise une action setup-node qui configure l'environnement de nœud à utiliser ;
    • Il exécute les scripts d'installation, de construction et de test trouvés dans notre fichier package.json.
  • coverage
    Cela utilise une action coverallsapp qui publie les données de couverture LCOV de votre suite de tests sur coveralls.io pour analyse.
emplois réussis
Tous les travaux s'exécutent avec succès. ( Grand aperçu )

J'ai d'abord fait un push vers ma branche feat-add-controllers-and-route et j'ai oublié d'ajouter le repo_token de Coveralls à mon fichier .coveralls.yml , donc j'ai eu l'erreur que vous pouvez voir à la ligne 132.

Échec de la construction
Échec du travail en raison d'une erreur dans le fichier de configuration des combinaisons. ( Grand aperçu )
 Bad response: 422 {"message":"Couldn't find a repository matching this job.","error":true}

Une fois que j'ai ajouté le repo_token , ma construction a pu s'exécuter avec succès. Sans ce jeton, les combinaisons ne seraient pas en mesure de rapporter correctement mon analyse de couverture de test. Heureusement, notre GitHub Actions CI a signalé l'erreur avant qu'elle ne soit poussée vers la branche principale.

Construction réussie
Erreur corrigée, travail réussi. Yay! ( Grand aperçu )

NB : Celles-ci ont été prises avant que je sépare le poste en deux postes. De plus, j'ai pu voir le résumé de la couverture et le message d'erreur sur mon terminal car j'ai ajouté l'indicateur --verbose à la fin de mon script de couverture

Conclusion

Nous pouvons voir comment mettre en place une intégration continue pour nos projets et également intégrer une couverture de test à l'aide des Actions mises à disposition par GitHub. Il existe de nombreuses autres façons de l'ajuster pour répondre aux besoins de votre projet. Bien que l'exemple de référentiel utilisé dans ce didacticiel soit un projet vraiment mineur, vous pouvez voir à quel point l'intégration continue est essentielle, même dans un projet plus important. Maintenant que mes tâches ont été exécutées avec succès, je suis sûr de fusionner la branche avec ma branche principale. Je vous conseillerais toujours de lire également les résultats des étapes après chaque exécution pour voir qu'il est complètement réussi.