Publié le

Mon sujet de stage: refonte d'un simulateur de véhicules

Stagiaire

Sujet de stage: Migrer l'interface Web du simulateur de véhicules vers une application React.js

Après avoir bien pris en main mon environnement de travail, il est maintenant temps de commencer le sujet principal de mon stage !

Contexte

Ce sujet porte sur la refonte de l'interface Web du simulateur de véhicules. Les membres de l'équipe m'ont proposé ce sujet de stage car la version actuelle de l'interface du simulateur est assez "brute".
En effet, le simulateur est développé en Elixir, ce programme s'occupe aussi bien de la partie logique (back-end) que visuelle (front-end). Cette partie front-end est rendue avec Phoenix, un framework Web pour Elixir, dans ce cas la il sert à utiliser des templates HTML, c'est-à-dire que l'on donne à notre programme un modèle de page HTML qui indique à Phoenix quelles données afficher et à quel endroit, Phoenix va donc ajouter les données nécessaires et renvoyer cette page HTML dans la réponse de la requête faite par le navigateur.


Templating HTML

Templating HTML


Le fait que cette technologie ne soit pas la plus adaptée pour cela et donc que l'interface ne soit pas réellement développée comme une application à part entière complexifie le fait d'avoir une interface sympathique et facile d'utilisation, de plus, ce n'est pas pratique à maintenir. Par exemple, pour changer de véhicule sur le simulateur, il faut obligatoirement retourner sur le menu d'accueil et sélectionner un nouveau véhicule, ou bien modifier l'URL manuellement, c'est qui est à éviter pour une bonne expérience utilisateur.
C'est donc pour ces raisons que je vais refaire, à partir de zéro, cette interface sous la forme d'une application React.js.


Version actuelle du simulateur

Version actuelle du simulateur


Les membres de l'équipe m'ont laissé carte blanche sur ce sujet, j'ai pu donc proposer moi-même le nouveau design, les librairies utilisées, etc.
Le fait de partir de zéro peut faire peur, mais c'est une très bonne façon de voir comment se construit réellement un projet de A à Z dans une entreprise !
Mais avant de commencer réellement le projet, il a fallu découper les tâches.

Découpage des tâches

Afin de bien structurer ce projet, une epic pour la refonte de l'interface a été créée sur notre board Jira.
Une epic est un ensemble de tâches qui peuvent être sous-divisées en tâches (tickets) plus petites, donc dans ce cas-là, l'epic porte le nom du but du projet, son nom sera donc "Migrate Paddock Web Interface to REACT".
Sur ce ticket d'epic, nous pouvons donc retrouver les différentes sous-tâches du projet. À la création du ticket, le Product Owner avait déjà ajouté quelques tâches principales, une fois que je serais à l'étape de développement de l'application je pourrais à mon tour ajouter de nouvelles tâches plus précises.


Sous-tâches de l'epic

Sous-tâches de l'epic


Proposer un nouveau design

Étant donné qu'un des buts principaux de cette refonte consiste à améliorer l'UI/UX de l'interface, il a donc fallu penser à un nouveau design pour cette application.
Pour cela, j'ai pu utiliser l'outil Figma afin de créer mes maquettes et prototypes. Cette partie peut paraître simple et rapide, mais en réalité il faut prendre en compte :

  • le fait que je n'ai pas beaucoup utilisé Figma auparavant et que je n'ai pas beaucoup de connaissances sur l'UI/UX
  • le temps de réflexion pour arriver à un design satisfaisant
  • demander et prendre en compte les retours des futurs utilisateurs

Pour mener à bien cette tâche, j'ai donc dû réfléchir à différentes problématiques telles que :

  • Comment rendre plus agréable et facile le changement de véhicule ?
  • Comment optimiser l'agencement des différents paramètres ?
  • Comment faciliter l'utilisation du simulateur ? (Barre de recherche ? Catégories ? etc)

Les membres de l'équipe ont pu m'aider à répondre à ces problématiques en me partageant les points qu'ils n'aiment pas sur la version actuelle et en me proposant des idées pour résoudre ces points. Par exemple, nous avons pu réfléchir ensemble à comment changer de véhicule facilement.
Pour rappel, pour changer de véhicule sur la version actuelle il faut soit revenir au menu d'accueil pour sélectionner un nouveau véhicule ou bien modifier l'URL de la page.
Pour résoudre ce problème nous avons alors retenu l'idée d'avoir un header (élément en haut de la page) permettant de choisir un véhicule. En utilisant un header, cela permettrait de changer de véhicule sans changer de page, tout en limitant le nombre de clics (1 seul clic contre 2 pour la version actuelle).

Une fois que nous avons pu nous mettre d'accord sur les idées des nouveaux comportements, j'ai pu réaliser une première version de la maquette sur Figma.
Une fois cette première maquette finie, j'ai pu profiter des réunions de daily, ou bien en organisation des réunions, pour montrer à l'ensemble de l'équipe cette maquette et ainsi chacun a pu donner son avis et me proposer des changements si besoin.
Suite à ces retours, j'ai pu modifier la maquette pour l'améliorer, et présenter de nouveau toutes les parties qui composent la maquette jusqu'à ce que j'arrive à un résultat satisfaisant pour tout le monde.

Voici donc la maquette retenue :


Maquette du simulateur

Maquette du simulateur


Quand il y a beaucoup de véhicules simulés et qu'ils ne rentrent pas tous dans le header, nous afficherons les véhicules restant dans un menu de sélection :


Maquette du simulateur avec beaucoup de véhicules

Quand nous sélectionnons un véhicule présent dans le menu de sélection, celui-ci prend l'apparence d'un bouton de sélection de véhicule comme les autres, avec une petite flèche qui laisse rappeler que l'on peut choisir d'autres véhicules en cliquant dessus :


Maquette du simulateur avec le menu de selection cho

Création des catégories de paramètres

Parallèlement à la création de la maquette, j'ai également classé tous les différents paramètres possibles du simulateur dans différentes catégories.
Cela va permettre une meilleure navigation dans l'application, et ainsi faciliter la recherche d'un paramètre particulier.
Pour cela, le Product Owner m'a présenté chaque paramètre en m'expliquant à quoi il servait, dans quel contexte l'utiliser, etc. Grâce à cela, nous avons pu choisir des premiers noms de catégories grâce aux thèmes importants qui ressortaient de ces différents paramètres.
Ensuite, j'ai pu affecter chaque paramètre à une catégorie et présenter ma proposition aux membres de l'équipe, ceux-ci ont pu me faire des retours sur cette catégorisation, ce qui m'a permis de modifier en conséquences ces catégories et leur contenu afin de faciliter au maximum l'utilisation de cette nouvelle interface.

Choix des technologies utilisées

Les membres de l'équipe m'ont laissé la liberté d'explorer les différentes technologies qui pourraient être utilisées pour ce projet, la seule technologie imposée est React.js car c'est avec cette technologie que nous développons nos applications Web.
Comme je commençais le projet de zéro, j'ai donc dû réfléchir à quelles technologies utiliser pour les différentes parties de l'application.
J'ai donc commencé par réfléchir à ces parties de l'application qui sont :

  • Le store (si vous ne savez pas ce que c'est, je l'ai expliqué dans cet article)
  • Les tests de l'application

Quel store pour cette application ?

La technologie utilisée pour la gestion de store sur l'application principale est Redux. L'idée d'utiliser Redux pour le simulateur peut donc venir assez facilement, cependant il faut bien réfléchir aux besoins de notre application.

Pour cela j'ai donc dû faire le point sur les besoins du simulateur au niveau du store :

  • L'application va récupérer les données des véhicules simulés via une API
  • Elle va ensuite afficher les paramètres des véhicules selon les données récupérées (par exemple: batterie à 75%)
  • Pour modifier une valeur, l'application va envoyer une requête à une API

J'ai donc pu constater que l'application allait gérer un état uniquement côté serveur, et non pas un état global.
Un état global englobe un état serveur et un état client, mais ces deux états sont en réalité différents :

  • État serveur
    • Les valeurs proviennent d'une source qu'on ne contrôle pas (par exemple: une API)
    • À besoin de faire des requêtes asynchrones pour récupérer et mettre à jour les données
  • État client
    • Les données qui sont spécifiques au côté client et qui ne dépendent pas des données d'un serveur (par exemple: des réglages d'interface graphique, un thème d'interface)

Pour choisir la librairie de gestion de store, j'ai donc cherché lesquels existaient et revenaient le plus, et j'ai donc fait une sélection des librairies qui pourraient être intéressantes pour cette application en me basant sur plusieurs critères tels que la maintenabilité, la facilité d'utilisation, nombre d'utilisateurs, rythme des mises à jours.

En premier temps, j'ai donc sélectionné ces librairies :

J'ai ensuite testé chacune de ces librairies pour voir comment celles-ci fonctionnaient, et ainsi retenir les côtés positifs et négatifs de chaque librairie pour le cas d'usage du simulateur.
Pour cela j'ai développé rapidement une petite application de Todo-List, cette application est un bon exemple pour tester ces librairies car elle inclut le principe du CRUD (Create, Read, Update, Delete).
Cet exemple permet donc de voir rapidement comment utiliser chaque librairie sur plusieurs points tels que la récupération des tâches via une API, la création d'une tâche, la mise à jour de l'état d'une tâche et la suppression d'une tâche.


Application de todo-list

Application de todo-list


Pour simuler l'API des todos, j'ai pu utiliser le service du site mockapi.io qui permet de mettre en place très rapidement une API simple.
Il suffit de créer le schéma de notre ressource (ici une tâche) et nous pouvons générer de nouvelles ressources qui seront accessibles via l'API.


Schema de ressource

Schema de la ressource


Cette API nous propose plusieurs endpoints et prend en compte plusieurs méthodes de requête:

  • Récupérer toutes les tâches (GET /todos)
  • Récupérer une tâche à partir de son ID (GET /todos/:id)
  • Créer une nouvelle tâche (POST /todos)
  • Modifier une tâche (PUT /todos/:id)
  • Supprimer une tâche (DELETE /todos/:id)

Ces endpoints vont donc nous être très utiles pour notre application de todos !

Une fois que j'ai pu tester toutes les librairies, j'ai donc pu me faire un avis sur chaque librairie en listant ses points forts et ses points faibles et en les comparant selon différents critères.

LibrairieType de storePoids (minifié)TéléchargementsRythme de MaJComplexité
Redux ToolkitGlobal43.7kB2.5 millions / semainePlusieurs par moisComplexe
RematchGlobal4.7kB (+ Redux Toolkit)39.5 mille / semainePas de MaJ depuis 2 ansComplexe (moins que Redux)
ValtioGlobal8.2kB300 mille / semainePlusieurs par moisSimple pour du client state
ZustandGlobal3kB2 millions / semainePlusieurs par moisSimple pour du client state
Tanstack QueryServeur55.3kB1.4 millions / semainePlusieurs par moisSimple pour du serveur state

À la suite de ces tests, la librairie que j'ai retenue comme la plus appropriée dans le cas d'usage du simulateur est : TanStack Query !
Ce choix s'explique par le fait que le simulateur n'a besoin que d'un état serveur, et TanStack Query est spécifiquement fait pour gérer les états serveurs, là où toutes les autres librairies s'occupent d'un état global.
Étant donné que TanStack Query est fait pour gérer l'état serveur, il apporte plus de fonctionnalités et est beaucoup plus simple à mettre en place et à utiliser que les autres librairies, il permet de :

  • Récupérer des données via une API très facilement, sans beaucoup de code contrairement aux autres librairies
  • Faire des mises à jour d'état optimistes, c'est-à-dire qu'il est possible de modifier l'état pendant que la modification est en train de se faire du coté du serveur.
    Cela permet d'améliorer l'expérience utilisateur en enlevant la latence du serveur pour mettre à jour un état, par exemple quand on ajoute une tâche, celle-ci va s'ajouter instantanément et non pas quelques millisecondes après.
    Si jamais la requête s'est mal passée et que l'état n'a finalement pas été mis à jour, TanStack Query permet d'annuler la mise à jour optimiste, et tout cela nativement et avec peu de code à mettre en place.

Pour utiliser TanStack Query, il suffit juste de créer un nouveau client :

client.ts
import { QueryClient } from "@tanstack/react-query";

export const queryClient = new QueryClient();

Une fois le client créé, on peut créer une fonction pour récupérer les tâches sur l'API

getAllTodos.ts
const URL = "https://647f415fc246f166da906fb8.mockapi.io/todos"

export const getAllTodos = async () => {
    const response = await fetch(URL);
    return response.json();
}

Et enfin pour récupérer ces données dans un composant, il suffit d'appeler notre requête avec le hook useQuery :

TodoList.tsx
import React from "react";
import { Todo } from "../../types/todo";
import { TodoItem } from "../TodoItem/TodoItem";
import { useQuery } from "@tanstack/react-query";
import { getAllTodos } from "../../api/getAllTodos";

/**
 * 
 * Displays a list of todo items
 */
export const TodoList = () => {
    const query = useQuery({ queryKey: ["todos"], queryFn: getAllTodos});

    if (query.isLoading) {
      return <h3>Loading</h3>
    }
  
    if (query.isError) {
      return <h3>Error</h3>
    }
    
    return(
        <>
        {query.data.map((todo: Todo) => {
            return (
                <TodoItem todo={todo} key={todo.id} />
            )
        })}
        </>
    )
}

Une fois mon étude sur ces différentes librairies terminée, j'ai organisé des réunions avec les membres de l'équipe pour leur partager mes découvertes, comment j'ai pu les tester, et présenter la librairie que je conseillerai d'utiliser.

Quelles technologies pour les tests de l'application ?

Parallèlement à mon étude sur les stores, j'ai aussi fait une étude sur les façons de tester l'application.
Les tests sont importants et servent à s'assurer du bon fonctionnement de notre application ainsi que de la non-régression lors de modifications, ceux-ci sont peuvent être automatisés ou non et répondent à plusieurs besoins différents, c'est pour cela qu'il existe différents types de tests.

  • Les tests unitaires
    • Les tests unitaires permettent de vérifier que notre composant se rend et se comporte bien de la manière voulue en vérifiant que le résultat et son comportement sont bien ceux attendus.
      Par exemple: tester un composant "Todo" en lui donnant différentes valeurs et en regardant si ces différentes valeurs sont bien affichées, si le status de la tâche est bien en "à faire" ou "fait" selon les valeurs données.
  • Les tests d'intégration
    • Les test d'intégration permettent de vérifier que plusieurs composants fonctionnent bien ensemble, qu'ils soient parfaitement intégrés à notre application.
      Ces tests peuvent être utilisés pour tester une fonctionnalité impliquant plusieurs composants.
  • Les tests fonctionnels
    • Les tests fonctionnels peuvent ressembler aux tests d'intégration, seulement ceux-ci sont plus d'un point de vue utilisateur.
      Là où un test d'intégration va tester s'il est possible de faire une action, le test fonctionnel va vérifier que le résultat de cette action est valide.
  • Les tests d'UI / Stories
    • Ces tests là ne sont pas automatisés.
      Ces tests permettent de tester des composants d'un point de vue visuel.
      Dans notre cas, nous utilisons ces tests comme un outil pendant les phases de développement, ils nous permettent de voir le comportement du composant que nous sommes en train de développer selon plusieurs paramètres (stories) et ainsi s'assurer qu'il s'intègre bien visuellement dans notre application.
      Selon l'outil utilisé, ces tests peuvent générer des snapshots, ce sont en quelque sorte des sauvegardes du composant tel qu'il est apparu.
  • Les tests de snapshots
    • Ces tests utilisent les snapshots générées précédemment, ils permettent de comparer le code actuel avec les snapshots créées avant nos modifications.
      Cela va permettre d'identifier les changements visuels, si les changements sont intentionnels nous pouvons alors mettre à jour les snapshots, si ce n'est pas le cas, c'est que nous avons modifié le visuel de quelque chose que nous n'aurions pas du.
  • Les test d'acceptance / bout en bout
    • Ces tests permettent de simuler le comportement d'un utilisateur final sur l'application complète.
      Ces tests utilisent un navigateur automatisé.

Heureusement, il n'y a pas besoin d'une technologie différentes par type de tests, en général ces technologies permettent de mettre en place la quasi-totalité de ces tests.
J'ai donc commencé à chercher les différentes technologies possibles pour ces tests, pour cela il a fallu prendre en compte les technologies qui seront utilisées par le simulateur, c'est-à-dire :

  • React.js
  • TypeScript
  • Vite.js
  • TanStack Query

Il existe donc plusieurs technologies qui seraient compatibles telles que :

  • Jest
  • Vitest
  • Cypress
  • Mocha
  • Jasmine
  • Chai

Tous ces choix fonctionnent différemment, sauf Jest et Vitest ! En effet Vitest a été développé de façon à être une alternative à Jest spécialisée pour Vite.js, de ce fait les tests écrits avec Jest et Vitest sont quasiment identiques.
Étant donné que nous utilisons actuellement Jest, il ne serait pas judicieux d'utiliser une librairie avec un fonctionnement complètement différent.
Jest répondant très bien aux besoins actuels, je n'ai pas donc pas trouvé utile de me pencher sur les autres technologies. De ce fait il nous reste :

  • Jest
  • Vitest

Comparé à Jest, l'avantage de Vitest est qu'il utilise le même fichier de configuration Vite entre l'application et les tests, de ce fait les tests se rapprochent au maximum du cas réel d'utilisation de l'application, là où Jest n'est pas capable d'utiliser cette même configuration.
Cet avantage permettrait à Vitest de faire tourner les tests plus rapidement qu'avec Jest, mais en réalité ce n'est pas toujours le cas comme peuvent en témoigner cet article ou bien cette issue sur le dépôt GitHub de Vitest.
Étant donné que ces résultats dépendent de la façon dont notre application est développée, j'ai pensé que l'on pourrait profiter du développement de cette nouvelle application, beaucoup plus légère que l'application de gestion de flotte, pour essayer cette alternative à Jest que nous utilisons actuellement.
Si le résultat s'avère concluant nous pourrons donc réfléchir à migrer nos tests Jest vers des tests Vitest, ce qui serait beaucoup plus simple que si on devrait migrer vers une librairie complètement différente.

Pour ce qui est des tests d'UI / Stories et les tests d'acceptance / bout à bout, il n'y avait pas d'alternative intéressante, j'ai donc décidé d'utiliser les mêmes technologies qu'actuellement qui sont :

  • UI / Story
    • Storybook
  • Acceptance / bout à bout
    • Script Python avec Selenium

J'ai donc partagé mes recherches et mes choix avec les membres de l'équipe lors de réunions de la même façon que pour le choix de la librairie de store.

Validation des choix


Schema de ressource

TanStack Query et Vitest


À la suite de ces réunions, les membres de l'équipe ont validé mon choix de librairie, j'ai alors pu en parler avec le Product Owner et le tech lead de l'équipe pour complètement valider ces choix.
Même si l'application de gestion de flotte utilise des librairies différentes, nous sommes partis du principe que le fait de développer de zéro l'interface du simulateur peut être une bonne idée pour explorer de nouvelles technologies, et potentiellement les utiliser dans l'application de gestion de flotte si le résultat est satisfaisant et que le cas d'usage est compatible.

Le développement de l'application

Une fois le choix des technologies utilisées terminé, il est maintenant temps de passer au développement de l'application !
Actuellement, le développement n'a pas encore commencé, en effet, au moment d'écrire ce post, nous venons de valider le choix des technologies qui seront utilisées pour cette application.

Afin d'avancer le plus facilement possible pour le développement, je vais devoir découper le projet en plusieurs tâches.
Une première tâche a déjà été créée par le Product Owner de l'équipe lors de la création de l'epic, celle-ci concerne le bootstrap de l'application.
Le bootstrap, c'est-à-dire la base de l'application, contient tous les fichiers nécessaires pour commencer le développement de l'application, cela inclut donc la base d'une application React.js ainsi que les librairies nécessaires. Une fois cette étape terminée, ce sera à mon tour de découper mes tâches ! On peut déjà imaginer quelques tâches comme :

  • Mettre en place le store
  • Créer le header
  • Créer le menu des réglages globaux
  • Créer les catégories
  • Créer le composant "Paramètre"
  • etc

Le but est donc de diviser le développement de l'application en plein de petits tickets, de façon à être organisé dans l'avancement du projet afin de ne pas se perdre et se fixer des objectifs concrets.

Même si le cette refonte n'est pas encore terminée, j'ai la chance de continuer en alternance au sein d'EasyMile après mon stage, ce qui me permettra de mener ce projet jusqu'au bout.