Dans un précédent article, j'ai présenté ma solution pour définir une seule commande qui sélectionne automatiquement les tests à lancer en fonction de l'état actuel des tests.
Voyons maintenant comment lancer ça par la pensée. Ou presque.
Je ne sais pas si vous avez fait du développement web avant la généralisation du hot reload mais si c'est le cas, il y a une suite de commandes que vous avez du taper de très nombreuses fois:
Je sauve mon code, je vais dans le navigateur, je recharge … mille fois par jour !
À force, on se rend compte que le cerveau traite ça comme une seule action. On ne décompose plus les différents gestes, la mémoire musculaire fait son boulot et il suffit de penser « voir le résultat » pour que nos mains s'activent et que le navigateur recharge la page.
J'ai depuis perdu cette habitude, travaillant principalement avec un terminal comme seul output, mais j'ai voulu conserver ce principe de boucle.
Je vous présente donc mon setup actuel dans Visual Studio Code, mais il est possible de faire la même chose avec n'importe quel éditeur capable d'intégrer un terminal comme Neovim ou Emacs.
Bon déjà, faites-moi le plaisir de mettre votre terminal VSCode sur le côté. L'avoir en dessous de la page bouffe une place pas possible!
Le code c'est vertical, il faut pouvoir le voir en entier. Vous écrivez peut-être du code parfait avec un maximum de trois lignes par fonction, mais ça n'est pas le cas de vos collègues ;)
Et le terminal non plus n'a pas besoin d'afficher des lignes de 200 caractères.
Nous allons configurer un raccourci pour lancer une commande dans le terminal de VSCode sans avoir à aller dans le terminal. On peut donc écrire du code, lancer la commande et continuer d'écrire sans avoir à gérer le focus du curseur.
Notez que le raccourci fonctionne aussi quand le curseur est dans le terminal.
J'ai choisi la combinaison Ctrl+Alt+U. « U » pour Unit Test puisque en général cette commande me sert à lancer les tests. Mais vous mettez évidemment ce que vous voulez.
Voici comment modifier les raccourcis dans VSCode:
\n
pour exécuter l'action dans le terminal.Et voici le code à ajouter. Ici j'ai repris la commande présentée dans mon précédent article.
{
"key": "ctrl+alt+u",
"command": "workbench.action.terminal.sendSequence",
"args": {
"text": "mix test --failed --max-failures 1 --seed $(cat _build/seed || echo 0) && mix test --stale --seed $(cat _build/seed || echo 0) && echo $RANDOM >! _build/seed\n"
}
},
Ce n'est pas très lisible, mais on va arranger ça.
À partir de maintenant, le workflow de test devient :
Et bien sûr gérer les cinquante autres problèmes qui surviennent pendant une session de code, mais c'est une autre histoire.
Vu que la commande de test va automatiquement lancer les tests que vous voulez, il n'y a plus besoin de réfléchir à quoi exécuter. (Dans 90% des cas… Il y a toujours des exceptions, c'est un peu la base de notre métier.)
Cette alternance entre écrire du code et vérifier le résultat se retrouve tout le temps, dans quasiment tous les projets. Et pas uniquement pour les tests. Par exemple quand on écrit de la documentation en markdown, quand on travaille sur un script de migration qui n'est pas vraiment testable, quand on s'arrache les cheveux sur un problème d'Advent Of Code, quand on travaille une commande cURL un peu velue, etc.
On travaille généralement dans une boucle, et avoir une commande unique pour lancer cette boucle est une bonne habitude à prendre pour qu'exécuter le code très souvent devienne un réflexe. Coder pendant 10 minutes sans rien tester est une mauvaise idée.
Et là vous me dites « tu es bien gentil, mais ta commande elle lance des tests Elixir, ça ne fonctionne pas avec mon Python pour AoC ou mon script bash pour générer mon site statique ».
Oui. C'est un problème, lancer les tests c'est cool mais on voudrait aussi pouvoir faire autre chose.
La solution pour ça est assez facile: On modifie le raccourci VSCode pour qu'il exécute un script, et ce script peut être différent pour chaque projet.
Lors de l'initialisation d'un projet, je crée un fichier dev-loop
à la racine
du projet:
touch dev-loop
chmod +x dev-loop
Après avoir copié la commande dans le fichier, il suffira d'exécuter
./dev-loop
pour l'exécuter.
Et voici le contenu de mon fichier, Avec la commande de test pour Elixir:
#!/usr/bin/bash -ve
trap exit INT
mix test --failed --max-failures 1 --seed $(cat _build/test-seed || echo 0)
mix test --stale --seed $(cat _build/test-seed || echo 0)
echo $RANDOM > _build/test-seed
Quelques notes:
/usr/bin/bash
quelque soit votre shell.-v
permet d'afficher les commandes exécutées. Je ne sais toujours
pas si je préfère avec ou sans!-e
permet d'arrêter le script dès qu'une
commande renvoie un code d'erreur. Cela permet de lister les commandes
indépendamment, enlève les && \
un peu moches, et laisse la possibilité
d'ajouter ou de commenter des lignes plus facilement.trap exit INT
permet au Ctrl+C d'arrêter
tout le script quand on interrompt l'exécution d'une des commandes (ce qui ne
renvoie pas un code d'erreur).Et on peut donc l'appeler avec notre raccourci :
{
"key": "ctrl+alt+u",
"command": "workbench.action.terminal.sendSequence",
"args": {
"text": "./dev-loop\n"
}
},
Le fichier étant créé dans un projet, il risque d'être commité dans Git. Ici plusieurs solutions :
.gitignore
mais il faudra expliquer en boucle de quoi il
s'agît car personne ne verra jamais le fichier apparaître.dev-loop
dans votre fichier .gitignore global (~/.gitignore
) et vous
pouvez ainsi créér dev-loop
dans n'importe quel projet Git sans souci.Le fait d'utiliser un fichier améliore pas mal de choses mais il reste un problème: après avoir fait une session TDD de quelques heures, on aimerait facilement changer de boucle, passer sur de la doc. Il faudrait que notre commande fasse temporairement autre chose afin de bénéficier de notre workflow bien pratique.
On pourrait imaginer plein de solutions, comme par exemple avoir plusieurs raccourcis, ou modifier une variable d'environnement via un script qui affiche un prompt… trop compliqué!
Je préfère avoir un setup modifiable et robuste. Il y aura toujours des douzaines de commandes à entrer manuellement de toutes façons.
Pour pallier cela, je définis des fonctions dans mon fichier dev-loop
:
#!/usr/bin/bash -e
trap exit INT
# task=dev
# task=dialyzer
task="${task:-test}"
task_dev()
{
mix run tmp/some-script.exs
}
task_dialyzer()
{
task_test
mix dialyzer
}
task_test()
{
mix test --failed --max-failures 1 --seed $(odo -c _build/seed.txt) --trace
mix test --stale --seed $(odo -c _build/seed.txt)
odo _build/seed.txt
}
task_$task
Je peux donc décommenter temporairement l'une de ces deux lignes pour changer de boucle:
# task=dev
# task=dialyzer
Et comme on est en bash, on peut en fait faire une infinité de choses. Ici par exemple, très simple, la tache qui exécute Dialyzer lance également les tests avant.
Il existe cependant d'autres possibilités pour exécuter une commande spécifique à chaque projet :
Utiliser les outils de build/run de l'éditeur. Je n'ai jamais pris l'habitude de faire ça mais je suppose qu'on peut avoir un résultat similaire. Je préconise quand même de configurer le run pour qu'il exécute un script, car le script est indépendant.
Utiliser un mode watch de votre outil de test. Il en existe un pour Elixir/ExUnit. Je n'ai pas vraiment trouvé cela intéressant. Sur le papier ça fonctionne bien, en réalité c'est vite frustrant car ça ne lance pas ce que je veux. Et cela garde le terminal occupé.
Utiliser des scripts globaux. Ma commande de test est quasiment la même sur tous les projets, donc cela pourrait être un script global qui s'exécute dans le dossier courant. Avec un autre script pour la doc, un autre script pour les projets JS, Python, etc.. Pourquoi pas!
Ce que j'aime avec l'utilisation d'un fichier c'est que ça continue de
fonctionner même dans un environnement de travail dégradé. Par exemple à
distance sur un serveur, ou avec un autre éditeur ; il suffit de lancer
./dev-loop
à la main, tant pis pour le raccourci.
Ou encore quand justement on travaille sur un script bash ou .exs
, ou sur une
grosse commande cURL
: I l suffit de mettre la commande dans le fichier.
C'est totalement adaptable à n'importe-quelle situation.
Oh et bien sur on peut utiliser des variables d'environnement ou passer des arguments au script.
Ce setup assez rapide à mettre en place permet d'avoir une commande personnalisée facilement modifiable pour chaque projet, propre à vous.
Une fois que votre mémoire musculaire se sera faite, vous la lancerez automatiquement en pensant « je veux voir si ça marche » … à condition d'avoir les mains sur le clavier.
Personnellement je ne peux plus m'en passer !
Voici comment configurer VSCode pour sauter entre le terminal et le code:
{
"key": "ctrl+l",
"command": "workbench.action.terminal.focus"
},
{
"key": "ctrl+l",
"command": "workbench.action.focusActiveEditorGroup",
"when": "terminalFocus"
},
Et pour Elixir uniquement, voici comment lancer uniquement le test dans lequel se trouve le curseur, et tout les tests du fichier en cours :
{
"key": "alt+u",
"command": "workbench.action.terminal.sendSequence",
"args": {
"text": "mix test ${file}:${lineNumber}\n"
}
},
{
"key": "shift+alt+u",
"command": "workbench.action.terminal.sendSequence",
"args": {
"text": "mix test ${file} --trace\n"
}
},