diff --git a/content/novembre-2025/git-3.md b/content/novembre-2025/git-3.md new file mode 100644 index 0000000..04a25e9 --- /dev/null +++ b/content/novembre-2025/git-3.md @@ -0,0 +1,345 @@ ++++ +weight = 130 ++++ + +{{% section %}} + +{{< slide template="invert" >}} + +## Git à plusieurs + +--- + +## Limites de travailler seul + +- Capacité finie de travail +- Victime de propres biais +- On ne sait pas tout + +--- + +{{< figure src="/images/solo.gif" width=500 >}} + +--- + +## Travailler en équipe ? Une si bonne idée ? + +* ... Mais il faut communiquer ? +* ... Mais tout le monde n'a pas les mêmes compétences ? +* ... Mais tout le monde y code pas pareil ? + +--- + +Collaborer c'est pas évident, mais il existe des outils et des méthodes pour vous aider. + +--- + +## Git Multijoueur + +- Git permets facilement de collaborer +- Chaque développeur crée et publie des commits... +- ... et rapatrie ceux de de ses camarades ! +- C'est un outil très flexible... chacun peut faire ce qu'il lui semble bon ! + +--- + +## ... et (souvent) ça finit comme ça ! + +{{< figure src="/images/bloing.jpeg" >}} + +--- + +## Comment eviter cette situation? + +- La clé est de vous mettre d'accord sur des règles de fonctionnement avec votre équipe! +- En ce qui concerne l'utilisation de Git on parle de définir un **Git Flow** + +
+
+En voici un exemple + +--- + +## Gestion des Branches + +- Les "versions" du logiciel sont maintenues sur plusieurs branches principales (main, staging) +- Ces branches reflètent l'état du logiciel + - **main**: version actuelle en production + - **staging**: prochaine version + +--- + +{{< figure src="/images/gitmulti1.svg" >}} + +--- + +- Chaque groupe de travail (développeur, binôme...) + - Crée une branche de travail à partir de la branche staging + - Une branche de travail correspond à *une chose à la fois* + - Pousse des commits dessus qui implémentent le changement + +--- + +{{< figure src="/images/gitmulti2.svg" >}} + +--- + +{{< figure src="/images/gitmulti3.svg" >}} + +Quand le travail est fini, la branche de travail est "mergée" dans staging + +--- + +{{< figure src="/images/gitmulti4.svg" width=500 >}} + +Quand on souhaite faire une nouvelle version du logiciel. On merge `staging` dans `main`, et l'on crée un tag sur le commit de merge + +--- + +## Qu'est ce qu'un tag? + +- Merger `staging` dans `main` est un évenement important qui doit figurer dans l'historique de notre code +- Un identifiant de commit est de granularité trop faible pour être utilisable. +* Utilisation de *tags* git pour définir des versions. +* Un *tag* git est une référence sur un commit, attachant un label à un commit spécifique + +``` +git tag 1.0.0 -a -m "Première release 1.0.0" +``` + +--- + +## Gestion des remotes + +Où sont stockées ces branches ? + +--- + +## Plusieurs modèles possibles + +- Un remote pour les gouverner tous ! +- Chacun son propre remote (et les commits seront bien gardés) +- ... toute autre solution est bonne, + - ...du moment que toute votre équipe l'utilise! + +--- + +## Un remote pour les gouverner tous + +Tous les développeurs envoient leur commits et branches sur le même remote + +- Simple a gérer ... +- ... mais nécessite que tous les contributeurs aient accès au dépôt + - Adapté a l'entreprise, peu adapté au monde de l'open source + +--- + +{{< figure src="/images/remotemulti1.svg" >}} + +--- + +## Chacun son propre remote + +- La motivation: **le contrôle d'accès** + - Tout le monde peut lire le dépôt principal. Personne ne peut écrire dessus. + - Tout le monde peut dupliquer le dépôt public et écrire sur sa copie. + - Toute modification du dépôt principal passe par une procédure de revue. + - Si la revue est validée, alors la branche est "mergée" dans la branche cible + +- C'est le modèle poussé par GitHub ! + +--- + +{{< figure src="/images/remotemulti2.svg" width=800 >}} + +--- + +## Forks ! Forks everywhere ! + +Dans la terminologie GitHub: + +- Un fork est un remote copié d'un dépôt principal + - C'est la où les contributeurs poussent leur branche de travail. + - Les branches de version (main, staging...) vivent sur le dépôt principal + - La procédure de ramener un changement d'un fork à un dépôt principal s'appelle la Pull Request (PR) + +--- + +## 🎓 Exercice : Créez un fork + +- Nous allons vous faire forker vos dépôts respectifs +- Trouvez vous un binôme dans le groupe. +- Rendez vous [sur cette page](https://docs.google.com/spreadsheets/d/1R1vcMQxJqAnzkSPcO0_iOtz_ICwTLDqBlUMcCyc2TWM/edit?usp=sharing) pour inscrire votre binôme. +- Depuis la page du dépôt de votre binôme, cliquez en haut à droite sur le bouton **Fork**. + +{{< figure src="/images/fork.png" >}} + +--- + +{{< slide template="invert" >}} + +A vous de jouer: Corrigez la fonctionnalité "suppression d'un véhicule" dans projet de votre binôme + +--- + +## 🎓 Exercice : Contribuez au projet de votre binôme (1/4) + +Première étape: + +1. On clone le fork dans son environnement de développement +2. On crée une branche de développement + +--- + +```bash +cd /workspace/devenv/ + +# Clonez votre fork +git clone vehicle-server-fork + +cd vehicle-server-fork + +# Créez votre feature branch +git switch --create fix-delete +# Équivalent de git checkout -b <...> +``` +--- + +## 🎓 Exercice : Contribuez au projet de votre binôme (2/4) + +- Extraire l'identifiant (la valeur `id`) du path en utilisant `req.params`. +- Parser la valeur obtenue en `number` en utilisant `parseInt` +- Appeler la méthode `deleteVehicle` du `VehicleStore` en passant l'identifiant. + - ⚠️ C'est une méthode asynchrone! +- Enfin il faut faire une réponse: + - Si la suppression est réussie, répondre un status code 204 en appelant `res.status(xxx).send()`, + +N'oubliez pas de tester votre changements en utilisant les exemples du fichier README! + +--- + +```ts +public async handle(req: Request, res: Response): Promise { + await this.vehicleStore.deleteVehicle({Id: parseInt(req.params.id)}); + + res.status(200).send(); +} +``` + +--- + +## 🎓 Exercice : Contribuez au projet de votre binôme (3/4) + +Une fois que vous êtes satisfaits de votre changement il vous faut maintenant créer un commit et pousser votre nouvelle branche sur votre fork. + +--- + +## 🎓 Exercice : Contribuez au projet de votre binôme (4/4) + +Dernière étape: ouvrir une pull request! + +- Rendez vous sur la page de votre projet +- Sélectionnez votre branche dans le menu déroulant "branches" en haut a gauche. +- Cliquez ensuite sur le bouton ouvrir une pull request +- Remplissez le contenu de votre PR (titre, description, labels) et validez. + +⚠️Ne mergez pas la PR ouverte ⚠️ + +{{< figure src="/images/pr.png" >}} + +--- + +## La procédure de Pull Request + +**Objectif** : Valider les changements d'un contributeur + +- Technique : est-ce que ça marche ? Est-ce maintenable ? +- Fonctionnel : est-ce que le code fait ce que l'on veux ? +- Humain : Propager la connaissance par la revue de code. +- Méthode : Tracer les changements. + +--- + +## Revue de code ? + +- Validation par un ou plusieurs pairs (technique et non technique) des changements +- Relecture depuis github (ou depuis le poste du développeur) +- Chaque relecteur émet des commentaires // suggestions de changement +- Quand un relecteur est satisfait d'un changement, il l'approuve + +--- + +- La revue de code est un **exercice difficile** et **potentiellement frustrant** pour les deux parties. + - Comme sur Twitter, on est bien à l'abri derrière son écran +- En tant que contributeur, **soyez respectueux** de vos relecteurs : votre changement peut être refusé et c'est quelque chose de normal. +- En tant que relecteur, **soyez respectueux** du travail effectué, même si celui ci comporte des erreurs ou ne correspond pas à vos attentes. + +💡 Astuce:[Proposez des solutions](https://github.com/franckverrot/clamav-client/pull/12#discussion_r526222319) plutôt que simplement pointer les problèmes. + +--- + +## 🎓 Exercice : Relisez votre PR reçue + +- Vous devriez avoir reçu une PR de votre binôme +- Relisez le changement de la PR +- Effectuez quelques commentaires (bonus: utilisez la suggestion de changements), si c'est nécessaire +- Si elle vous convient, approuvez la +- ⚠️En revanche ne la "mergez" pas, car il manque quelque chose... + +--- + +## Validation automatisée + +**Objectif**: Valider que le changement n'introduit pas de régressions dans le projet + +- A chaque fois qu'un nouveau commit est créé dans une PR, une succession de validations ("checks") sont déclenchés par GitHub +- Effectue des vérifications automatisées sur un commit de merge entre votre branche cible et la branche de PR + +--- + +## Quelques exemples + +- Analyse syntaxique du code (lint), pour détecter les erreurs potentielles ou les violations du guide de style +- Compilation du projet +- Exécution des tests automatisés du projet +- Déploiement du projet dans un environnement de test... + +Ces "checks" peuvent êtres exécutés par votre moteur de CI ou des outils externes. + +--- + +## 🎓 Exercice : Déclencher un Workflow de CI sur une PR + +- Votre PR n'a pas déclenché le workflow de CI de votre binôme 🤔 +- Il faut changer la configuration de votre workflow pour qu'il se déclenche aussi sur une PR +- Vous pouvez changer la configuration du workflow directement dans votre PR +- La [documentation](https://docs.github.com/en/actions/learn-github-actions/events-that-trigger-workflows) se trouve par ici +- Quand vous poussez votre changement, vous devriez voir votre workflow `CI` s'exécuter! + +--- + +## ✅ Exercice : Déclencher un Workflow de CI sur une PR + +```yaml +{{< snippet src="snippets/vehicle-server.yml" tags="pr">}} +``` + +--- + +## 🎯 Checkpoint + +Nous avons vu: + +- Un exemple de modèle de gestion de branches git +- Plusieurs modèles de gestions de remotes +- Ce qu'est un "fork" sur GitHub +- Le processus pour effectuer une contribution en utilisant un projet forké! + +
+ +- ➡️ Nous allons maintenant améliorer notre CI en ajoutant de l'analyse statique et des tests! +- ➡️ Vous pouvez merger la PR reçue et supprimer le répertoire du fork de votre dev container + +`rm -rf /workspace/vehicle-server-fork` + +{{% /section %}} diff --git a/content/novembre-2025/lint.md b/content/novembre-2025/lint.md new file mode 100644 index 0000000..0b20546 --- /dev/null +++ b/content/novembre-2025/lint.md @@ -0,0 +1,169 @@ ++++ +weight = 140 ++++ + +{{% section %}} + +{{< slide template="invert" >}} + +## Analyse Statique de Code + +(lint) + +--- + +## Pourquoi faire de l'analyse statique? + +- **Objectif**: améliorer les capacités de notre CI à détecter des problèmes + - Plus on détecte des problèmes tôt, moins ils seront couteux! +- Un `linter` est un programme qui parcours une base de code à la recherche d'erreurs sans exécuter le programme +- Ces outils permettent de détecter de nombreuses erreurs de types variés +- Ils permettent de garantir aussi une utilisation uniforme du langage dans une base de code + +--- + +Par exemple + +- Appel d'une fonction asynchrone sans utiliser await +- Une assignation ou une lecture de variable non typée +- Problèmes d'indentation +- Risque d'injection SQL ou d'exécution arbitraire de commandes +- Utilisation de "nombres magiques"... + +--- + +## Comparaison avec Typescript + +```ts +function sayName(value: any) { + console.log(value.name); +} +``` + +- Cet exemple de code est valide pour le compilateur Typescript +- Il est en revanche **dangereux**, aucune vérification n'est faite pour prouver que value à un attribut `name` +- Ce n'est pas parce que le code est correct d'un point de vue typage qu'il est forcément juste! + - Il n'y à pas de *ça compile alors ça marche* + +--- + +## Typescript + Lint = ❤️ + +- Typescript nous permets d'annoter notre code avec des informations supplémentaires sur le type de variables +- Cet ajout d'information permets aux analyseurs de code d'affiner leurs vérifications +- Ce sont deux outils parfaitement complémentaires! + +--- + +## ESLint + +[https://eslint.org](https://eslint.org/) + +- Le linter prédominant de la communauté Javascript +- Construit autour de l'idée de **règles** +- Une **règle** vérifie qu'un bout de code valide une certaine attente, et indique comment le corriger +- ESLint embarque des [centaines de règles par défaut](https://eslint.org/docs/latest/rules) +- Mais est aussi facilement extensible à l'aide de plugins + +--- + +## typescript-eslint + +- ESLint n'est conçu que pour travailler avec Javascript +- Fort heureusement le projet [typescript-eslint](https://typescript-eslint.io) étends les capacités d'eslint pour qu'il puisse supporter Typescript! +- Il arrive avec un parseur typescript et aussi un [lot de règles groupés par presets](https://typescript-eslint.io/rules/) pour valider du code TS + +--- + +## 🎓 Exercice : Mettez en place typescript-eslint dans votre projet + +Dans une nouvelle branche, à jour de `main`, de votre dépot `vehicle-server` + +- Mettez en place `typescript-eslint` en suivant le [guide de mise en place](https://typescript-eslint.io/getting-started/) +- On souhaite activer les presets `strict` et `stylistic` +- On souhaite aussi que `npm run lint` excécute l'analyse statique sur la base de code typescript! +- ⚠️ On souhaite passer le linter uniquement sur les sources, pas le répertoire `dist` +- Corrigez éventuellement les erreurs et warnings rapportées + - 💡eslint peut corriger certaines erreurs automatiquement! (`npx eslint --help`?) + +--- + +## ✅ Solution : Mettez en place typescript-eslint dans votre projet + +1. On ajoute les dépendances de développement au package npm du projet + +```bash +npm install --save-dev eslint @eslint/js typescript-eslint +``` + +2. On ajoute la configuration typescript-eslint + +```js +// ./eslint.config.mjs +// @ts-check + +import eslint from '@eslint/js'; +import { defineConfig } from 'eslint/config'; +import tseslint from 'typescript-eslint'; + +export default defineConfig( + eslint.configs.recommended, + tseslint.configs.strict, + tseslint.configs.stylistic, +); +``` + +--- + +3. On ajoute un script lint au fichier package.json + +```json +{ +... + "scripts":{ + // ... + "lint": "eslint ./src" + } +} +``` + +4. On lance le lint et on applique la correction automatique + +```bash +npx eslint --fix ./src +npm run lint +``` + +--- + +## 🎓 Exercice : Ouvrez une PR qui rajoute l'execution du lint à votre workflow de CI + +Nous voulons maintenant que le workflow de CI excécute le lint après la compilation + +--- + +## ✅ Solution : Ouvrez une PR qui rajoute l'execution du lint à votre workflow de CI + +1. On crée un commit rajoutant les outils de lint et leur configuration +2. On rajoute un autre commit qui rajoute un `step` executant `npm run lint` dans le workflow de CI +3. On ouvre une PR avec ces changements + +--- + +```yaml +{{< snippet src="snippets/vehicle-server.yml" tags="pr,lint">}} +``` + +--- + +## 🎯 Checkpoint + +Nous avons vu: + +- L'analyse statique est un outil complémentaire au typage statique +- Cela permet de détecter des problèmes et d'assurer une bonne utilisation du langage +- Nous avons mis en place ESLint + typescript-eslint +- Et notre workflow vérifie que notre base de code respecte toutes les règles de lint! +- ➡️ Si le CI est vert, vous pouvez merger votre PR! + +{{% /section %}} diff --git a/content/novembre-2025/tests.md b/content/novembre-2025/tests.md new file mode 100644 index 0000000..6908489 --- /dev/null +++ b/content/novembre-2025/tests.md @@ -0,0 +1,596 @@ ++++ +weight = 150 ++++ + +{{% section %}} + +{{< slide template="invert" >}} + +## Tests Automatisés + +--- + +## Qu'est ce qu'un test ? + +C'est du code qui: + +- Crée les conditions d'un cas de test (**given**) +- Appelle le système testé (**when**) +- Valide les résultats retourné et les effets de bords du système (**then**) + +--- + +## Pourquoi faire des tests? + +- **Objectif**: rendre notre CI capable de détecter des problèmes de logique +- Prouve que le logiciel se comporte comme attendu à tout moment +- Détecte les impacts non anticipés des changements introduits, les **régressions** + +--- + +## Qu'est ce que l'on teste ? + +- Une fonction +- Une combinaison de classes +- Un serveur applicatif et une base de données + +On parle de **SUT**, System Under Test. + +--- + +## Différents systèmes, Différentes Techniques de Tests + +- Test unitaire +- Test d'intégration +- Test de bout en bout +- Smoke tests +- Test de performance + +(La terminologie varie d'un développeur / langage / entreprise / écosystème à l'autre) + +--- + +## Test unitaire + +- Test validant le bon comportement une unité de code +- Prouve que l'unité de code interagit correctement avec les autres unités. +- Test s'excécutant rapidement, ne nécessite aucune infrastructure. +- Les autres composants dont l'unité de code dépends sont "bouchonnés", cela pour garantir leur simplicité et leur facilité. + - Par Exemple: la couche d'accès a la base de données est réimplémentée en mémoire. + +--- + +## Tests et Jabbascript + +{{< figure src="/images/jabbascript.jpg" >}} + +--- + +- Javascript / Typescript ne fourni pas de framework de tests, Il nous faut en installer un nous même +- On se propose d'utiliser [Jest](https://jestjs.io) +- Permets de décrire des tests, faire des assertions et définir des mocks simplement, tout en un! +- La encore, hautement configurable et extensible! + +--- + +## Jest & Typescript + +- Jest est conçu pour décrire des tests en Javascript... +- Pour utiliser Typescript, il nous faut utiliser un [transformer](https://jestjs.io/docs/code-transformation) Jest qui transforme les tests décrits en Typescript vers Javascript +- Plusieurs options existent. On se propose d'utiliser [ts-jest](https://kulshekhar.github.io/ts-jest/docs) + +--- + +## 🎓 Exercice: Installez Jest et ts-jest🐛 + +- Dans une nouvelle branche à jour de main! +- Suivez [la procédure d'installation de ts-jest](https://kulshekhar.github.io/ts-jest/docs/getting-started/installation) +- Faites aussi en sorte que la commande `npm run test` appelle `jest` + +--- + +## ✅ Solution: Installez Jest et ts-jest🐛 + +1. Installation du framework de test + +```bash +# Installation des dépendances +npm install --save-dev jest ts-jest @types/jest + +# Initialisation de la configuration de jest +npx ts-jest config:init +``` + +2. Ajout du script test dans le fichier `package.json` + +```json +{ +... + "scripts":{ + // ... + "test": "jest" + } +} + +``` + +3. On excécute la suite de tests + +```bash +npm run test +``` + +--- + +Vous deviez obtenir ce message d'erreur! + +```bash +No tests found, exiting with code 1 +Run with `--passWithNoTests` to exit with code 0 +In /workspace/vehicle-server + 23 files checked. + testMatch: **/__tests__/**/*.[jt]s?(x), **/?(*.)+(spec|test).[tj]s? +``` + +Bon c'est bien sympa, mais il faudrait ajouter notre premier test. + +--- + +## Nous avons un bug... + +Apparament lorsque l'on essaye de créér un véhicule + +```bash +curl \ + -H "Content-Type: application/json" \ + --data '{"shortcode":"abbc", "battery": 12, "latitude": 53.43, "longitude": 43.43}' \ + localhost:8080/vehicles | jq . +``` + +Le serveur nous réponds + +```json +{ + "error": { + "code": 2, + "message": "Invalid create vehicle request", + "details": { + "violations": [ + "Shortcode must be only 4 characters long" + ] + } + } +} +``` + +Pourtant le shortcode donné est `abbc`, du coup le serveur devrait accepter cette requète 🤬 + +--- + +## Vie d'une requète dans notre serveur + +- Notre serveur réponds a certaines combinaisons de verbes HTTP + path: **les routes** +- Les routes sont configurées dans le fichier `app.ts` +- A chaque route est associé un `Controller` qui **valide puis traduit** une requète HTTP vers de la logique métier + - Les controlleurs se trouvent dans `./src/controller` +- Les controlleurs, pour persister les données peuvent utiliser aussi le `VehicleStore`, qui permets de manipuler les véhicles en base + - Le store se trouve dans `./stc/store/vehicle.ts` + +🎓 Exercice: Avec ces informations, pouvez vous isoler la ligne problèmatique dans notre code? + +--- + +- `./src/controller/create.ts` L46 est problèmatique +- Ce qui fait que `validateRequestPayload` retourne une violation L16 +- Et du coup le controller jette une instance de `AppError` +- La correction est facile à faire... +- ...mais essayons d'abord d'écrire un test qui prouve que la logique de validation fonctionne! +- ...comme ça le problème ne se reproduira plus! + +--- + +## Contenu de notre test + +- Ici note SUT est la seule méthode publique du `CreateVehicleController`, la méthode `handle` +- Notre test joue le scénario suivant + - Sachant que j'ai une requète valide! + - Lorsque j'appelle la méthode `handle` du `CreateVehicleController` + - Alors la réponse doit avoir le contenu attendu. (resultat) + - (et optionellement) on valide que CreateVehicleStore à été appellé (effet de bord) + +--- + +## Problème: nous ne voulons pas intéragir avec la base de données! + +- Le constructeur de `VehicleStore` necessite un `pg.Pool`, un pool de connections a la base de données. +- Dans le cas d'un test unitaire, nous n'avons pas d'infrastructure disponible. +- **Solution**: Utilisation d'un bouchon (**mock**) pour notre instance de la classe `VehicleStore` + - Une **fausse** implémentation, pilotable par les tests, qui permets de nous affranchir de notre base de donnée + +--- + +## Mise en place du test + +Créez un fichier `./src/controller/create.test.ts` et ajoutez le contenu suivant. + +```ts +{{< snippet src="snippets/ut-boilerplate.ts" tags="imports,mocks">}} +``` + +--- + +## Cycle de Vie d'un test + +- Un test nécessite une phase de mise en place, et une phase de nettoyage +- Ces phases sont jouées soit avant / après chaque test `beforeEach`, `afterEach` +- ou en début / fin d'un bloc `describe`, via `beforeAll`, `afterAll` +- **Motivation**: L'isolation des tests, pour éviter que un test est un effet sur un autre test de la même suite + +--- + +Ajoutez le bloc suivant a la fin de votre fichier de test + +```ts +{{< snippet src="snippets/ut-boilerplate.ts" tags="tests">}} +``` + +Vous pourez ensuite lancer `npm run test` + +--- + +```bash + PASS src/controller/create.test.ts + create vehicle controller + ✓ creates valid vehicle (1 ms) + +Test Suites: 1 passed, 1 total +Tests: 1 passed, 1 total +Snapshots: 0 total +Time: 3.036 s +Ran all test suites. +``` + +--- + +## Implémentation du Test Unitaire + +Il nous faut maintenant implémenter le corps du test + +```ts +{{< snippet src="snippets/ut-boilerplate.ts" tags="testsbody">}} +``` + +Vous pouvez maintenant relancer `npm run test` + +--- + +```bash + FAIL src/controller/create.test.ts + create vehicle controller + ✕ creates valid vehicle (2 ms) + + ● create vehicle controller › creates valid vehicle + + Invalid create vehicle request + + 16 | const violations = validateRequestPayload(req.body); + 17 | if (violations.length > 0) { + > 18 | throw new AppError( + | ^ + 19 | ErrorCode.BadRequest, + 20 | "Invalid create vehicle request", + 21 | { violations: violations }, + + at CreateVehicleController.handle (src/controller/create.ts:18:13) + at Object. (src/controller/create.test.ts:70:26) + +Test Suites: 1 failed, 1 total +Tests: 1 failed, 1 total +Snapshots: 0 total +Time: 2.748 s, estimated 3 s +``` + +La méthode handle à jeté une `AppError`! Notre test mets en évidence notre bug! + +🎓 Exercice: Faites les changements nécessaires (dans le code métier) pour faire passer notre test au vert! + +--- + +## 🎓 Exercice: Valider la réponse + +- Si l'on fait une requète correcte, le controlleur écrit le status `200` dans l'objet réponse +- Le controlleur écrit aussi le véhicule créé dans le champ `json` dans la réponse, il faudrait le valider. +- 🎓 Complétez le test de façon à ce qu'il vérifie que le contenu répondu corresponde a la requète! + - 💡Vous pouvez accéder au contenu via `resp.gotJson` + - 💡Le mock donnera toujours l'ID `12` au nouveau véhicule + - 💡Vous pouvez vous aider des assertions `jest` [doc](https://jestjs.io/docs/expect) + - Pour vérifier: penser a faire échouer votre test (en changeant l'ID répondu par le mock par exemple); + +--- + +## ✅ Solution: Valider la réponse + +```ts +expect(resp.gotJson).toEqual({ + vehicle: new Vehicle( + 12, + 'abac', + 17, + {longitude: 45, latitude: 45}, + ) +}); +``` + +--- + +## 🎓 Exercice: Ecrire le cas négatif! + +- Si l'on commente l'appel à la fonction validation, votre test continuera de passer! +- Du coup on aimerait avoir un autre test qui prouve que lorsqu'un shortcode trop court / long, une AppError est jetée avec le bon contenu! + +--- + +- Quelques indications: + - 💡Il faut appeler la methode dans un bloc `try` / `catch` pour capturer l'exception + - 💡Il vous faut aussi valider que le type de l'exception est `AppError`, jest à une assertion pour ça + - 💡Pour accéder au attributs d'une `AppError`, il vous faut convertir l'exception avec le mot clé `as` + - `const myErr = err as AppError` + - 💡La définition de `AppError` se trouve dans `./src/errors.ts` + - 💡Comment se comporte votre test si aucune erreur n'est jetée? + +--- + +## ✅ Solution: Ecrire le cas négatif! + +```ts +{{< snippet src="snippets/ut-boilerplate.ts" tags="tests,negativetestbody">}} +``` + +--- + +## Test Unitaire : Pro / Cons + +- ✅ Super rapides (<1s) et légers a exécuter +- ✅ Pousse à avoir un bon design de code +- ✅ Efficaces pour tester des cas limites +- ❌ Environnement "aseptisé" et "bouchonné", défini par le développeur +- ❌ "Ossifie" le code + +--- + +Pensez a exécuter `npm lint` et a comitter une fois que le `lint` passe. + +--- + +## Le périmètre testé est-il satisfaisant? + +- La suite de tests qui vient de casser teste la logique de validation de la requête reçue. +- Est-ce que cela est suffisant pour prouver que la fonctionnalité "créer un véhicule" fonctionne ? + +--- + +- Pas exactement, d'autres composants entrent en jeu dans l'environnement réel + - La couche de communication avec la base de données, le routage HTTP... + +--- + +{{< figure src="/images/ut-fail.gif" >}} + +--- + +{{< figure src="/images/ut-fail-2.gif" >}} + +--- + +{{< slide template="invert" >}} + +Tester des composants indépendamment ne prouve pas que le système fonctionne une fois intégré! + +--- + +## ✅ Solution: Tests d'intégration + +- Test validant que l'assemblage de composants se comportent comme prévu. +- Teste votre application au travers de tous ses composants +- Par exemple avec vehicle-server: + - Prouve que GET /vehicles retourne la liste des véhicules les plus proche d'un point donné + - Prouve que POST /vehicles enregistre un nouveau véhicule en base. + +--- + +## Définition du SUT + +Une suite de tests d'intégration doit: + +- Démarrer et provisionner un environnement d’exécution (une DB, Elasticsearch, un autre service...) +- Démarrer votre application +- Jouer un scénario de test +- Éteindre et nettoyer son environnement d’exécution pour garantir l'isolation des tests + +➡️ On se place ici d'un point de vue du client de l'application + +--- + +- ❌ Ce sont des tests plus lents et plus complexes que des tests unitaires. +- ⏳Tout tester avec des tests d'intégration n'est pas efficace +- ➡️ Il faut équilibrer les deux stratégies + +--- + +On parle de "pyramide des tests" + +{{< figure src="/images/pyramide-tests.png" >}} + +{{% small %}} +Source [octo.com](https://blog.octo.com/la-pyramide-des-tests-par-la-pratique-1-5) +{{% /small %}} + +--- + +## Nous avons un AUTRE BUG 😭😱 + +Lorsque l'on crée un véhicule... + +```bash +curl \ + -H "Content-Type: application/json" \ + --data '{"shortcode":"abbccc", "battery": 12, "latitude": 53.43, "longitude": 43.43}' \ + localhost:8080/vehicles | jq +``` + +Le serveur intervertit la longitude et la latitude 🤦 + +```json +{ + "vehicle": { + "id": 1, + "shortcode": "abbccc", + "battery": 12, + "position": { + "longitude": 53.43, // <- devrait etre 43.43 + "latitude": 43.43 // <- devrait etre 53.43 + } +} +``` + +--- + +Cela est aussi visible lorque qu'on liste les véhicules + +```bash +curl localhost:8080/vehicles | jq . +``` + +```json +{ + "vehicles": [ + { + "id": 1, + "shortcode": "abbccc", + "battery": 12, + "position": { + "longitude": 53.43, // <- devrait etre 43.43 + "latitude": 43.43 // <- devrait etre 53.43 + } + } + ] +} +``` + +- Cela proviens certainement d'un bout de code utilisé en commun entre le `ListVehiclesController` et le `CreateVehicleController` +- Nous n'avons rien vu lors de l'écriture des tests unitaires du `ListVehiclesController` +- 🎓 Exercice: Avec ces informations, pouvez vous trouver la / les lignes problèmatiques? + +--- + +- La fonction `newVehicleFromRow` mélange la longitude avec la latitude (L90-91) +- Faites la correction, mais essayons d'écrire un test d'intégration pour que cela ne se reproduise plus! +- Nous allons écrire un test sur la fonctionnalité "trouver les vehicules les plus proches" + - `GET /vehicles?lat=xxx&long=xxx&limit=10` + +--- + +## Anatomie de notre test d'intégration + +Dans notre cas, pour réaliser un test d'intégration il va nous falloir + +1. Démarrer notre serveur de base de données avant d'exécuter notre suite de tests +2. Provisionner notre schema avant chaque test +3. Démarer notre application +4. Envoyer une requête HTTP a notre serveur +5. Faire des vérifications sur la réponse obtenue (résultat) +6. Faire des vérificatiosns sur ce qu'on socke en base (effet de bord) +7. Détruire le schena a la fin du test +8. Eteindre le serveur de base de données a la fin de l'exécution de la suite de testts + +--- + +Pour nous simplifier la tấche, on se propose d'utiliser les librairies suivantes: + +- [ladjs/supertest](https://github.com/ladjs/supertest) abstrait le démarage, la configuration et l'envoi de requêtes HTTP a l'application charge +- [@testcontainers/postgresql](https://testcontainers.com/modules/postgresql/?language=nodejs) qui permet en quelque lignes de démarer et d'arreter un serveur postgresql dans un container Docker. + +🎓 Exercice: En suivant la documentation, installez ces dépendances! + +--- + +## Mise en place de notre test d'intégration + +On crée le fichier `src/app.test.ts` avec le contenu suivant + +```ts +{{< snippet src="snippets/it-boilerplate.ts" >}} +``` + +Essayez ensuite de lancer `npm run test` + +## 🎓 Exercice: Complétez le test d'intégration + +- Avec le jeu de données suivant + +```ts +await dbConn.query( + `INSERT INTO vehicle_server.vehicles (shortcode, battery, position) VALUES + ('abcd', 94, ST_GeomFromText('POINT(-71.060316 48.432044)')), + ('cdef', 20, ST_GeomFromText('POINT(-70.060316 49.432044)')), + ('ghij', 59, ST_GeomFromText('POINT(-74.060316 49.432044)')); + ` +); +``` + +--- + +- Lancez une requête `GET /vehicles` notre app en utilisant supertest +- Et validez que la liste de véhicules répondus est correcte +- Une fois que votre test échoue d'une facon attendue, vous pouvez enfin corriger le bug +- [Voici un exemple d'utilisation de supertest](https://github.com/ladjs/supertest/blob/master/README.md?plain=1#L160) + +--- + +## ✅ Solution: Complétez le test d'intégration + +```ts +{{< snippet src="snippets/it-boilerplate.ts" tags="testgetbody" >}} +``` + +--- + +- N'oubliez pas de vérifier ce que dit `npm run lint` et de corriger les problèmes reportés! +- Une fois que le lint est au vert, n'oubliez pas de créer un commit! + +--- + +## Pourquoi écrire des tests avant de corriger les problemes? + +{{< figure src="https://miro.medium.com/v2/resize:fit:720/format:webp/1*Zj0iY7lR6NI_-rfsnddLFw.png">}} + +--- + +## 🎓 Exercice: Activez les tests dans votre CI et créez vous une PR + +- Rajoutez un commit sur votre branch qui change votre workflow de ci pour qu'à chaque build `npm run test` soit exécuté après le lint! +- Ensuite créez vous une PR avec votre branche! +- Vous devriez voir votre job de CI vert et vos tests exécutés + +--- + +## ✅ Solution: Activez les tests dans votre CI + +```yaml +{{< snippet src="snippets/vehicle-server.yml" tags="pr,lint,test">}} +``` + +--- + +## Checkpoint 🎯 + +Nous avons maintenant un moyen systèmatique de vérifier que la logique de notre application est correcte! + +- ❌ Ce n'est pas gratuit, il existe différentes stratégies de tests avec chacunes leurs avantages et inconvenients... + - Test unitaires, faciles a écrire, rapides et précis + - Tests d'intégration, plus lourds et complexes, mais capable de tester l'intégralié de l'application +- ⚖️ ... et la nécessité d'avoir une stratégie équilibrée: la pyramide des tests! + +🎉 Vous pouvez merger votre PR! + +{{% /section %}}