Qualité de Développement - R4.02

Première mise en pratique du TDD.

Afin de mettre en pratique le TDD et de poursuivre la prise en main du langage Rust, vous allez développer deux petits projets :

  • un programme capable de déterminer si une chaîne de caractères est un palindrome.

  • une petite bibliothèque de géométrie permettant de manipuler des points et des vecteurs 2D.

Mise en place de Git, du workflow et de l’intégration continue

Dans le dépôt Git que vous avez créé pour la matière, vous allez, pour chaque TP, créer une branche dédiée.

De plus, pour chaque projet, vous mettrez en place un workflow de développement basé sur :

  • La branche dédiée, avec éventuellement des sous branches pour les différentes fonctionnalités.

  • Une "merge request" depuis la branche dédiée visant à intégrer les modifications correspondant au TP dans la branche principale, main.

Création de la branche et du package

Avant de vous lancer dans le code, vous devez créer une branche pour le palindrome nommée palindrome. Vous pouvez le faire depuis l’interface GitLab ou bien dans votre dépôt local.

Une fois la branche créée, basculez dans celle-ci puis créez un package Rust nommé tp2_palindrome avec la commande :

cargo new tp2_palindrome

Création de la "merge request" pour la branche

Afin de pouvoir identifier clairement les contributions au dépôt qui concernent le TP, vous allez créer une "merge request" pour la branche palindrome. Les push se feront vers la branche source de cette merge request, c’est-à-dire la branche palindrome et déclencheront les pipelines GitLab.

Une fois votre TP terminé, vous pourrez accepter la merge request.

Mise en place de l’intégration continue

Pour l’intégration continue, vous allez utiliser les pipelines GitLab.

Pour cela, vous devez ajouter un fichier .gitlab-ci.yml à la racine de votre dépôt.

Ce fichier vous permet de configurer les pipelines qui seront lancés à chaque push sur votre branche.

Voici un exemple de fichier vous permettant de configurer un pipeline pour le TP palindrome :

image:
  name: rust:1.83.0-bookworm

workflow:
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
      when: never
    - if: $CI_COMMIT_BRANCH

test-palindrome:
  stage: test
  script:
    - echo "Testing palindrome lab"
    - cd tp2_palindrome
    - cargo test

La section image permet de spécifier l’image Docker à utiliser pour les jobs du pipeline. Ici, nous utilisons l’image rust qui contient l’environnement de développement Rust.

La section workflow permet de définir les règles qui déclenchent les jobs. Le but ici est de ne lancer les tests que sur les push sur la branche qui est la source d’une merge request.

La section test-palindrome définit le "job" qui lance les tests.

Le résultat du pipeline sera celui du test.

Validation du workflow

Une fois votre workflow en place, validez que celui-ci fonctionne en commitant et poussant votre projet qui doit contenir le package tp2_palindrome.

Cela doit bien sûr être fait dans la branche palindrome.

Une fois le push fait, validez que le pipeline se déclenche bien et que les tests passent depuis l’interface GitLab. Vos pipelines sont dans la rubrique "Build > Pipelines" de votre dépôt.

Vous devriez voir quelque chose comme ça dans la liste de vos pipelines :

pipeline running

puis après quelques instants :

pipeline passed

Si vous cliquez sur le statut du pipeline, vous devriez voir quelque chose comme :

pipeline details

Palindrome

Spécifications

Votre programme devra vérifier les contraintes suivantes à la fin du TP :
  • Détecter qu’une chaîne de caractères est un palindrome : c’est-à-dire qu’elle correspond au même mot à l’envers.

    • Des mots comme "non" ou "radar" sont des palindromes.

    • Des mots comme "aspirateur" ou "petit" ne sont pas des palindromes.

  • La casse n’a pas d’influence sur le fait d’être un palindrome : "Non" ou "Radar" sont également des palindromes.

  • Il devra également être capable de détecter des phrases palindromes comme "Was It A Rat I Saw" ou "Engage le jeu que je le gagne".

Mise en pratique du TDD sur le problème du palindrome

Règles du TDD

Vous allez mettre en pratique le TDD classique "Red-Green-Refactor". Les règles sont :

  1. Vous devez écrire un test qui échoue avant de pouvoir écrire le code de production correspondant.

  2. Vous devez écrire une seule assertion à la fois, qui fait échouer le test ou qui échoue à la compilation.

  3. Vous devez écrire le minimum de code de production pour que l’assertion du test actuellement en échec soit satisfaite.

Premier test - "Write a failing test"

Créez votre premier test dans le fichier main.rs, en dessous de la fonction main, de votre nouveau projet. Adaptez le nom du test à l’exigence que vous testez :

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn radar_is_palindrome() {
        assert!(is_palindrome("radar"));
    }
}

Votre IDE devrait vous offrir la possibilité de passer les tests, sinon avec cargo :

$ cargo test
...
error[E0425]: cannot find function `is_palindrome` in this scope
...

Vous devriez constater que l’étape de compilation ne passe logiquement pas. Vous êtes arrivés à la fin de la première étape du cycle TDD :

TDD RGR

Bravo ! vous avez pratiqué la première étape du TDD. Mais ce n’est pas fini : il va falloir continuer le cycle.

Code pour le premier test - "Make the test pass"

Votre tâche est maintenant d’écrire le minimum de code pour que le test passe. Dans un cas simple comme celui du palindrome, vous serez peut-être tentés de commencer à écrire des choses compliqués. Ce n’est pas la bonne façon de faire dans le cas général.

Le premier bout de code doit ressembler à ça :

fn is_palindrome(chaine: &str) -> bool {
    true
}

Constatez que le test passe maintenant.

Le principal intérêt de ce test est de valider le prototype de la fonction et de valider que les outils de test fonctionnent.

Vous êtes arrivés à la deuxième étape du cycle. Ce serait le moment de faire du refactoring. En l’occurrence le code et les tests sont tellement simples qu’il n’y a rien à modifier.

À ce stade, vérifiez que votre pipeline fonctionne bien en commitant et poussant votre projet.

Vous pouvez voir les détails de ce qui s’est passé en explorant les information sur le "job" de votre pipeline.

Itérez

Maintenant que le test passe, revenez au début du cycle : Écrire un nouveau test qui échoue.

Pensez à faire un commit à chaque itération ! C’est-à-dire à chaque fin du cycle TDD.

Vous n’avez pas besoin de ré-écrire tout le module de test, uniquement d’ajouter une fonction avec l’annotation #[test]

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn radar_is_palindrome() {
        assert!(is_palindrome("radar"));
    }

    #[test]
    fn aspirateur_is_not() {
        assert!(!is_palindrome("aspirateur"));
    }
}

Écrivez un code qui fait passer les tests

Ne vous souciez pas tout de suite des autres contraintes sur le palindrome, uniquement ce qui fera passer les tests.

Une version valide pourrait être :

fn is_palindrome(chaine: &str) -> bool {
    if chaine == "radar" {
        return true;
    } else {
        return false;
    }
}

Ou sa version plus idiomatique :

fn is_palindrome(chaine: &str) -> bool {
    chaine == "radar"
}

Mais on va arrêter la torture à ce stade, écrivez un premier algorithme qui détecte un mot palindrome.

Refactoring

À ce stade vous avez une première version de l’algorithme de détection de palindrome qui fait passer deux tests.

Vous êtes donc dans la phase de refactoring. Il peut être utile à ce stade d’examiner votre code et vos tests pour vérifier si une amélioration ne pourrait pas être faites. Si vous décidez de modifier votre code ou vos tests, vous devez valider que les tests continuent de passer avant d’écrire un nouveau test.

Terminez le détecteur de palindrome

Continuez d’itérer dans le cycle TDD jusqu’à respecter les spécifications.

Pensez à faire un commit à chaque itération ! C’est-à-dire à chaque fin du cycle TDD.

Considérations de qualité

Prenez le temps d’évaluer :

  • La confiance que vous avez dans votre code.

  • L’impact qu’aurait l’ajout d’une contrainte en terme de remise en question de l’existant.

  • Le risque associé à l’optimisation de votre algorithme de détection.

  • La facilité pour un développeur à comprendre les garanties offertes par votre code.

Pensez à vérifier régulièrement que votre pipeline fonctionne bien.

Une fois que vous avez terminé, fusionnez votre branche dans la branche main depuis l’interface GitLab, dans la rubrique "Merge requests"

Créez un tag pour marquer la fin de votre travail sur le palindrome.

S’il vous reste du temps

Si vous avez terminé, fusionné et tagué votre travail sur le palindrome, ajoutez un exécutable à votre projet pour générer des palindromes.

Vous aurez besoin d’une liste de mots français. Vous pouvez en trouver une ici : https://www.pallier.org/extra/liste.de.mots.francais.frgut.txt