#! TDD en Bash – (Serious business)



#! TDD en Bash – (Serious business)

0 0


TestsAutomatisesEnBash-Presentation-RevealJs

Lightning Talk sur les tests automatisés en Bash

On Github MichaelBorde / TestsAutomatisesEnBash-Presentation-RevealJs

#! TDD en Bash

(Serious business)

Michael Borde / @michael_borde / Arpinum

Bonjour à tous et merci d'être présent. Je suis Michael Borde, développeur passionné chez Arpinum. Nous sommes un atelier logiciel bordelais et nous travaillons pour des clients sympas. Je vais vous parler d'un hobby peu recommandable : les tests automatisés en Bash.

Le contexte

© DC Comics Il y a peu, je travaillais sur un plateau de développement et j'étais en charge, avec mon équipe, d'améliorer le quotidien de tous les développeurs. Ca en jette non?

Quelques chiffres

  • 119 entrepôts
  • 2,3 Millions de lignes de Java
  • 282 projets Maven
  • 4566 commits / mois
  • 52 auteurs actifs
Ils étaient suffisamment nombreux pour que ce travail soit aussi intéressant que compliqué. Nous avions, par exemple, un peu moins de 300 projets sur une centaine d'entrepôts.

Retournons quelques temps en arrière...

Mais prenons la DeLorean et retournons quelques temps en arrière.

L'ère ClearCase

© Roger Hargreaves Avant il y avait ClearCase, mais ça c'était avant. ClearCase c'est Monsieur Lent, il fait des choses parfois, mais qu'est-ce qu'il est lent! Surtout quand il faut faire appel à un autre département pour faire la moindre opération d'administration... Mais ça c'est un problème d'organisation pas de SCM :)

Puis l'ère du possible avec Git

© Sharknado Un beau jour, face à tout ce gâchis et sous l'impulsion d'un manager, nous avons un peu contournées les règles pour migrer nos sources sur Git. Et là tout devint possible, y compris se faire bouffer par un requin en prenant l'avion pour Agile Grenoble!

Et si nous automatisions les opérations répétitives et sensibles ?

Puisque c'était possible, nous avons alors décidé d'automatiser certaines opérations répétitives et sensibles du quotidien.

Un outil simple : Bash

En faisant le tour de nos postes de développements et des différents serveurs, le plus simple était d'utiliser Bash.

3 fois rien finalement

  • Cloner des entrepôts
  • Mettre à jour des sources
3 fois rien finalement : cloner ou mettre à jour en masse des entrepôts Git.

Mais avec un peu d'imagination

  • Créer/supprimer des branches
  • Créer/supprimer des jobs Jenkins
  • Lister les entrepôts à fusionner
  • Réaliser la livraison des projets
  • etc.

18 vaillants scripts

Mais bien vite nous avons imaginé de nombreux cas d'utilisations : créer des branches git ou des jobs Jenkins en masse, livrer les projets de manière automatique, etc. Au final nous avons un peu moins de 20 scripts.

Et il y en a pour tout le monde

Développeurs, administrateurs, livreurs, etc.

Ils concernent un peu tout le monde : développeurs, administrateurs, etc.

Dans des contextes multiples

avec un mode de tests manuels, mais chut!

Ils sont utilisables sur un poste de développement Windows, un serveur Linux ou via un job Jenkins. Il y a même un mode de tests manuels pour vérifier qu'on ne va pas déclencher l'enfer en situation réelle.

Deux exemples

./execute -p "*reference*" -y jar -b integration "mvn clean install"

./assigne-version -c auto "3.4.2-SNAPSHOT"
          
Je vous donne un exemple de script qu'il m'arrive d'utiliser plusieurs fois par jours. Pour tous les projets qui s'appellent plus ou moins "reference", de type "jar" et sur la branche "integration", exécute la commande "mvn clean install". Et en dessous un autre script plus malin. Pour tous les projets dont le cycle de vie est géré en "automatique", assigne la version "3.4.2-SNAPSHOT".

Les conseils de Google

If you are writing a script that is more than 100 lines long, you should probably be writing it in Python instead. Nous avons appliqué les préconisations Bash des équipes Google, sauf une peut-être : "si vous écrivez un script de plus de 100 lignes, vous devriez surement l'écrire en Python."

En Python !

© Toute la production de Dawson's Creek En python ! Il est malheureux Dawson, il n'a pas envie de faire du Python, il veut sortir avec Katie Holmes comme tout le monde. Quelqu'un fait du Python dans la salle? J'aime mieux faire du cheval, c'est plus large.

Eh oui, scripter en Bash c'est un peu...

Mais c'est compréhensible. Quand je regarde des exemples de code sur le net je me dis que scripter en Bash c'est un peu de chance et beaucoup de Stackoverflow. Heureusement dans les situations désespérées il reste le Nécronomicon.

Un exemple de Bash

https://github.com/creationix/nvm

(Node Version Manager - Simple bash script to manage multiple active node.js versions)

Pour se donner une petite idée, nous allons regarder le code d'un "script bash simple pour gérer les versions de Node.js".

Nous avons 4796 lignes de Bash !

Et pourtant nous avons accumulé près de 5000 lignes de Bash.
Bash n'est pas le problème de fond.

Il faut tacler la complexité

Notre ennemi est la complexité que nous produisons nous même.

Et si Bash était un langage de programmation ?

Et si Bash n'était pas un langage de script mais un langage de programmation comme les autres ?

Les outils habituels

Alors je dégainerais TDD, XP et Clean Code.

Clean Code, concrètement ?

Il est tout à fait possible, en Bash, de respecter le principe de responsabilité unique, d'avoir du code sans duplication et qui exprime l'intention.

Un équivalent à JUnit ?

Par contre, existe t-il un équivalent à JUnit ?

Bof...

Après avoir regardé des projets comme Bats ou ShUnit, je n'étais pas beaucoup emballé!

Je vais créer mon api de tests automatisés en Bash !

Alors j'ai décidé de créer ma propre api de tests automatisés en Bash ! Serious Business !

Une si bonne idée...

Une si bonne idée! Vous me voyez annoncez aux collègues un beau matin : "Nous allons faire du TDD en Bash avec une API sur mesure et nous serons payés pour ça!". Je reformule : "Hey les mecs, j'ai une idée : on va faire un BBQ dans la piscine!". Ils m'ont dit "banco".

Les objectifs de l'api

  • Favoriser TDD
  • Ressembler à xUnit
  • Compatible avec l'IC
  • Respecter la notion de tests unitaires
  • En français
L'objectif numéro 1 de l'API était de favoriser TDD. Mais je souhaitais aussi quelle ressemble à xUnit, soit compatible avec Jenkins et respecte la notion de tests unitaires. Si en plus elle pouvait être en français comme le reste de notre code, ça serait parfait.

Make them first

  • Fast
  • Isolated !
  • Repeatable !
  • Self-verifying !
  • Timely
En m'appuyant sur la définiton FIRST de test unitaire, je voulais me concentrer sur les 3 points du milieu : isolé, répétable, auto-vérifiant. Isolé, est une gageure par exemple. En Bash nous pouvons facilement modifier des variables globales, créer ou détruire des arborescences. J'ai eu mon quota de surprises désagréables.

Et donc ?

Et donc voici quelques exemples de l'API. Ils sont un peu édulcorés pour faciliter la compréhension.

L'exécuteur de test

Le grand stratégaire est l'exécuteur de test.

Exécuter tous les fichiers de test

function executeur_executeLesFichiersDeTestDansLeRepertoire() {
  local repertoire="$1"
  _initialiseLExecutionDesTests
  _executeTousFichiersDeTest "${repertoire}" "*Test.sh"
  _afficheLeResultatDesTests
  _retourneUnCodeEnFonctionDuResultatDesTests
}
          
L'exécuteur exécute tous les fichiers de tests qui respectent un certain motif pour un répertoire donné. Il affiche le résultat global des tests et retourne un code en fonction de l'exécution.

Exécuter un fichier de test

function _executeLeFichierDeTest() {
  local fichier="$1"
  source "${fichier}"
  local fonctions=("$(_fonctionsPubliquesDansLeFichier "${fichier}")")
  _executeFonctionSiPresente "avantTousLesTests" "${fonctions[@]}"
  _executeTousLesTests "${fonctions[@]}"
  _executeFonctionSiPresente "apresTousLesTests" "${fonctions[@]}"
}
          
Pour un fichier de test, il récupère les fonctions publiques qui par convention seront les fonctions de test. Il exécute tous les tests entre les setups et teardowns globaux si présents.

Exécuter une fonction de test

function _executeLaFonctionAuMilieuDuSetupEtTeardown() {
  local fonction="$1"
  shift 1
  _executeFonctionSiPresente "avantChaqueTest" "$@" \
  && ${fonction} \
  && _executeFonctionSiPresente "apresChaqueTest" "$@"
  _analyseLExecutionDuTest "${fonction}" $?
}
          
Pour une fonction de test, il l'exécute entre le setup et teardown si présents et analyse le code retour.

Quelques affirmations

Une API de test automatisés ne serait rien sans ses affirmations.

Affirmer l'égalité

function affirmation_affirmeEgalite() {
  local attendu="$1"
  local obtenu="$2"
  if [[ "${attendu}" != "${obtenu}" ]]; then
    _affirmationEnErreur "Obtenu : ${obtenu}, attendu : ${attendu}."
  fi
}
          
Voici une affirmation triviale pour l'égalité. C'est le plus simple qui fonctionne. J'affirme que le résultat obtenu correspond au résultat attendu. Sinon j'échoue avec un message d'erreur sympa.

Affirmer le succès

function affirmation_affirmeSucces() {
  ( $@ )
  if (( $? != 0 )); then
    _affirmationEnErreur "La commande a échoué au lieu de réussir."
  fi
}
          
Une autre pour affirmer le succès d'une commande. C'est très utile puisque les fonctions Bash ne retournent que des codes d'erreur.

Quelques exemples

Quelques exemples de tests.

Un test unitaire

function recupereBienLaVersionDunPom() {
  local pom="${_ressources}/pom_1.0.xml"

  local version="$(maven_recupereLaVersionDuPom "${pom}")"

  affirmation_affirmeEgalite "1.0" "${version}"
}
          
Un premier test unitaire qui concerne le module dédié à Maven. Etant donné un pom.xml en version 1.0, si je demande la version de ce pom, j'obtiens bien 1.0. Pour ceux qui ne connaissent pas Maven, un pom est un descripteur XML à l'intérieur duquel on trouve une propriété version. On remarque qu'il est possible en Bash de respecter le Arrange Act Assert pour améliorer la lisibilité des tests. L'exemple ici est trivial mais il est toujours possible de faire des extractions de fonctions pour les cas plus complexes.

Un test d'intégration

function ilEstPossibleDeChangerLaBrancheDUnEntrepot() {
  entrepot_cloneLaBrancheDeLEntrepot "integration" "module-a"

  source "${REPERTOIRE_SCRIPT}/checkout" -p "mod*" "master"

  local branche="$(_recupereLaBrancheCouranteDeLEntrepot "module-a")"
  affirmation_affirmeEgalite "master" "${branche}"
}
          
Un exemple plus ambitieux avec un test d'intégration. Je clone l'entrepôt module-a sur la branche integration, je demande un changement vers la branche master de tous les entrepôts qui commencent par mod et j'affirme que module-a est sur la branche master.
Exemple de sortie console Et pour finir, un exemple de sortie console avec de belles couleurs et un résumé de l'exécution en bas.

Reprenons les statistiques

Lignes % Production 2797 58% Tests 1671 35% API de tests 328 7% Total 4796 100% Quand on regarde la répartition des 5000 lignes de Bash, on s'aperçoit qu'il y a un tier de test. La couverture doit être moins bonne que sur d'autres projets mais pour des opérations proches du système, je trouve ça plutôt satisfaisant.

Et pour le fun

Nombre Temps Tests unitaires 91 13 s Tests d'intégration 109 10 min Voici la répartition des TU/TI. Nous avons beaucoup trop de tests d'intégration à mon goût et ils sont longs. C'est symptomatique de la programmation système. L'inversion de contrôle y est assez complexe mais pas impossible. Quoiqu'il en soit, ces tests nous assuraient une certaine sérénité.

Et ensuite?

Et ensuite?

... ensuite naquit shebang_unit.http://github.com/arpinum/shebang_unit

Ensuite nacquit shebang_unit que j'ai partagé sur Github. C'est la petite soeur libre de droit et en Anglais de l'API que je viens de présenter. Nous allons un peu l'étudier.

Pour finir

  • Qualité ❤
  • DIY !
  • Du fun, plein
Grace à ma petite aventure, j'ai bien retenu que qu'importe les technologies manipulées, il est toujours possible de faire du développement de qualité. Il faut simplement en avoir le courage. Le manque d'outil n'est certainement une excuse valable, en tant que développeur je peux facilement créer mes propres outils. Et comme je suis un professionnel je n'ai pas besoin d'avoir l'aval de mon manager. Pour finir je tenais à souligner qu'il faut mettre l'emphase sur le plaisir au travail. Si je fais la gueule en arrivant le matin, il y a de forte chanse que mon produit fasse aussi la gueule.

Fin

Ceci conclu mon intervention. Merci.
#! TDD en Bash (Serious business) Michael Borde / @michael_borde / Arpinum Bonjour à tous et merci d'être présent. Je suis Michael Borde, développeur passionné chez Arpinum. Nous sommes un atelier logiciel bordelais et nous travaillons pour des clients sympas. Je vais vous parler d'un hobby peu recommandable : les tests automatisés en Bash.