➡️ Activez la cloche, vous ne manquerez plus aucun de mes posts
➡️ Suivez les hashtag du moment :
#ytreza_dev` : pour suivre tout ce que j'explique
#dojo_ytreza_dev : pour suivre l'actualité du dojo
#formation_tdd_by_jonathan : pour suivre l'actualité autour de ma formation à TDD
➡️ Si vous pensez que le contenu peut apporter quelque choses aux autres, partagez le (avec ou sans un avis)
➡️ Si vous avez des questions sur le message, commentez-le
➡️ Si vous n'êtes pas d'accord avec le message ou s'il y a des erreurs, commentez-le
➡️ Si vous souhaitez passer à l'action, connectez-vous à mon réseau. Qui sait, le fait d'échanger ensemble nous permettra de changer le monde
➡️ Pour d'autres choix, c'est par ici : ytreza.dev
]]>Le process #TDD est le suivant : red, green, refactoring.
L'étape RED consiste à mettre en évidence que notre code ne fait pas ce que l'on attend de lui. Pour cela, nous allons mettre en place un test qui provoque .... une erreur !
Dès le départ, TDD nous confronte aux erreurs. Aux erreurs de comportement (le code ne fait pas ce que l'on attend), mais aussi aux erreurs de compilation (le code n'est pas correctement écrit) et aux exceptions (le code se trouve dans un contexte imprévu).
Lorsqu'on rencontre une erreur, on a deux options : l'affrontement ou la fuite. Affronter une erreur, c'est comprendre la signification de l'erreur, comprendre pourquoi elle se produit et comprendre comment la résoudre.
En pratiquant TDD, on se met dans un contexte où l'erreur n'est pas notre ennemie. Au contraire, elle est une alliée qui nous permet d'apprendre et d'avancer.
Prêt à faire des erreurs ?
]]>J'ajoute une étape de clarification entre l'étape Green et Refactoring. Cela permet de tricher et de gagner en vélocité dans le développement. On comprend mieux le code. Le schéma ci-dessous explique tout le processus TDD en détail.
Si vous pratiquez TDD, vous devez connaître le processus TDD : red - green - refactoring.
L'étape red correspond à la mise en place d'un test qui échoue. Cette étape sert à mettre en évidence qu'une fonctionnalité ne marche pas. L'étape green vient juste après et correspond à la mise en place du code qui fait réussir le test. Cette étape sert à mettre en place la fonctionnalité.
On pourrait s'arrêter là et développer notre projet en suivant des étapes de red et de green.
Mais au bout d'un moment, si vous faîtes comme ça, vous allez commencer à pédaler dans la semoule. En effet, si vous ne prenez pas soin du code, vous allez commencer à avoir un gros tas de boue dans lequel il va être compliqué d'avancer.
C'est pourquoi, on a une étape de refactoring. C'est dans cette étape que vous devez améliorer le code pour continuer à être rapide dans votre développement.
Que l'on me donne six heures pour couper un arbre, j'en passerai quatre à préparer ma hâche. Citation (apocryphe ?) d'Abrahm Lincoln
Dans la citation ci-dessus, préparer sa hâche correspond au refactoring et couper l'arbre correspond aux étapes red et green.
J'ai mis en place cette quatrième étape pour pouvoir optimiser le temps que l'on passe dans l'étape Green. Pour passer l'étape green, il faut écrire le bout de code minimal faisant passer le test. Or, cette étape prend souvent trop de temps pour les développeurs qui débutent en TDD. Ils réfléchissent au code qu'ils doivent écrire avant de l'écrire. Or, cette étape green doit être passée le plus rapidement possible.
Comment faire ?
Il faut tricher ! Et oui, j'ai bien dit Tricher.
La triche est un des concepts les plus importants (à mon avis) de TDD. Renvoyer une valeur encodée, faire un copier coller où je ne sais quoi qui peut faire passer le test du rouge au vert en moins de 30 secondes peut faire penser à de la triche. Mais en faisant cela, on vient d'ajouter une nouvelle fonctionnalité à notre code.
OK, alors, je rajoute un if avec la valeur encodée et je fais du TDD ?
Tout à fait, cela fait partie de la pratique TDD. Mais elle n'est pas terminée sinon, on se retrouverait avec une infinitité de if et de valeur encodée dans notre code. Il faut rajouter une étape, la clarification.
Cette étape ressemble énormément au refactoring, mais il y a une nuance.
Le refactoring consiste à changer la structure du code sans modifier le comportement. Malheureusement, lorsqu'on triche, le comportement attendu n'est pas tout à fait en place. Il peut manquer de la généricité. Il faut donc...
]]>Il intègre la dernière version du module wxUnit et il est plus léger puisque j'ai supprimé les gabarits.
Bon katas !
]]>Tout est dans la vidéo. Pour ceux qui préfèrent la lecture, vous pouvez aller directement sur la page des formations.
]]>L'intérêt d'utiliser ce builder est de standardiser la création d'un variant et de pouvoir l'écrire en une seule ligne. Cela peut être très utile pour rendre les tests beaucoup plus simple à lire. Cet outil permet aussi de créer des variants dynamiquement.
Voici quelques exemples :
v is variant
v.text = "some text"
v.'other text' = "other text"
v.values[1] = 10
v.values[2] = 20
v.values[3] = 30
v.items["a"] = "A"
v.items["b"] = "B"
est équivalent à la syntaxe suivante :
cVariantBuilder.create()...
.with_sub_value("text", "some text")...
.with_sub_value("other text", "other text")...
.with_sub_value("values", cVariantBuilder.create()...
.with_value(10)...
.with_value(20)...
.with_value(30))...
.with_sub_value("items", cVariantBuilder.create()...
.with_item("a", "A")...
.with_item("b", "B"))...
.build()
On peut aussi créer la procédure :
PROCEDURE a_variant() : cVariantBuilder
RESULT cVariantBuilder.create()
Ce qui nous permet d'écrire :
a_variant()...
.with_sub_value("text", "some text")...
.with_sub_value("other text", "other text")...
.with_sub_value("values", a_variant()...
.with_value(10)...
.with_value(20)...
.with_value(30))...
.with_sub_value("items", a_variant()...
.with_item("a", "A")...
.with_item("b", "B"))...
.build()
Et enfin, on peut tout mettre sur une seule ligne (ce que je ne conseille pas forcément).
a_variant().with_sub_value("text", "some text").with_sub_value("other text", "other text").with_sub_value("values", a_variant().with_value(10).with_value(20).with_value(30)).with_sub_value("items", a_variant().with_item("a", "A").with_item("b", "B")).build()
J'espère qu'il vous plaira.
]]>Je me suis mis à Rust il n'y a pas très longtemps et je suis tombé amoureux de ce langage. En général, je n'aime pas les relations compliquées, mais Rust m'a fait du charme et je n'ai pas su y résister.
Je travaillais donc avec WinDev et je me suis rendu compte que la gestion du YAML était buggé. Je pense que ce sera réglé un jour, mais je me suis dit que je pourrais corriger ça en utilisant le package YAML de Rust. J'ai donc réfléchi à comment faire dialoguer WinDev et Rust ensemble.
Je sais que WinDev peut manipuler des DLL et j'ai constaté que Rust peut générer des DLL. Cool, il ne reste plus qu'à trouver comment faire.
Cette histoire en plusieurs parties me servira d'aide mémoire pour la prochaine fois.
Pour créer une dll dans rust, c'est assez simple, il faut avoir un fichier lib.rs
et dans ce fichier, une fonction qui renvoie un résultat. Pour me simplifier la vie, je pars du principe que ce sera toujours une chaîne de caractère (les prochains épisodes de cet article nous diront pourquoi, mais gardons le secret pour la prochaine fois).
Voici le code de la fonction dans le fichier lib.rs
:
#[no_mangle]
pub extern fn call_rust() -> *const u8 {
"hello world".as_ptr()
}
#[no_mangle]
indique à Rust de ne pas modifier le nom de la fonction. On pourra ainsi appeler la fonction call_rust
depuis notre projet WinDev.pub extern
indique que la fonction call_rust
est publique et peut être appelé depuis la dll.*const u8
indique que l'on retourne un pointeur vers une chaîne de caractère (je sais, ce n'est pas très explicite).return
n'est pas obligatoire.On va aller modifier le fichier Cargo.toml de notre projet et indiquer le code suivant
[lib]
name = "test_dll"
crate-type = ["cdylib"]
[lib]
indique que l'on est en train de faire une bibilothèquename = "test_dll"
le nom de cette bibliothèque est test_dll
crate-type = ["cdylib"]
indique que l'on va faire une bibliothèque compatible avec COn effectue un coup de cargo build --release
et on se retrouve avec une jolie dll qui s'appelle test_dll.dll
Pour fonctionner, j'ai du créer un projet WinDev en 64 bits. La dll ne fonctionnait pas dans un projet 32 bits. Il doit surement y avoir une solution, mais je ne l'ai pas encore trouvée.
C'est tout simple avec le code suivant :
address is system int = CallDLL32("Chemin\de\la\dd\test_dll.dll", "call_rust")
Trace(StringRetrieve(address, srASCIIZAddress))
Et un joli hello world
s'affiche.
Et voilà, nous savons désormais créer une dll en Rust et l'utiliser avec WinDev. Je pensais que ce serait plus compliqué, et bien non ! Nous verrons dans le billet suivant comment créer une fonction qui nous permettra de remplacer la...
]]>Il y a quelques jours, je travaillais pour un de mes clients, et je devais intervenir sur une fenêtre pour laquelle je ne pouvais pas utiliser la base de données (je ne l'ai jamais paramétré chez moi). Je me suis souvenu que chaque fois que les développeurs travaillaient sur ce projet, le temps de lancement du go pouvait prendre plusieurs dizaines de seconde avant d'afficher quoique ce soit. On a même cru que la fenêtre buggait tellement elle mettait de temps à s'afficher.
J'aurais pu paramétrer la base de données comme tout le monde et tomber dans un monde où un simple test met une plombe à se lancer. Mais vous commencez peut-être à me connaître, je suis fainéant et je n'aime pas attendre. J'ai donc réfléchi à un moyen d'avoir une fenêtre qui s'ouvre rapidement sans cette fichue base de données.
J'ai donc appliqué le principe D de SOLID (si vous ne connaissez toujours pas, cliquez ici et lisez le paragramhe DIP) et j'ai réfléchi à comment inverser les dépendances et que ma fenêtre ne dépende plus de cette base.
Avant de rentrer dans le côté technique, il faut savoir que cette méthode me permet aussi de tester plusieurs scénarios sur ma fenêtre. Je peux tester ma fenêtre avec aucun enregistrement dans la base de données, avec 10, 20, 100 et ajouter tous les scénarios qui me viennent à l'esprit. Je peux simuler une erreur de la base de données au moment de l'ajout par exemple.
Par contre, cette technique peut se révéler compliquée à mettre en place si vous travaillez directement avec des tables reliées à un fichier. En effet, il faut enlever les dépendances et ce lien est une dépendance. Il faudra le faire sauter si vous souhaitez appliquer cette technique.
Bien, parlons techniques.
tl;dr :
L'idée de base est d'utiliser la compilation conditionnelle et le polymorphisme pour séparer le cas réel du cas de test. Et si vous n'avez pas envie de lire, vous pouvez suivre la formation vidéo (plus d'information dans la conclusion).
Vous êtes toujours là ? C'est parti !
On commence par créer une classe. Personnellement, avec ma convention de langage, je l'appelle c + le nom de la fenêtre + le préfixe final (c'est une convention temporaire).
Nom de fenêtre : wdClient
Nom de classe : cWdClientFinal
Ensuite, j'instancie cette classe dans les globales de la fenêtre
PROCEDURE MaFenêtre()
comportement is cWdClientFinal dynamic = créerComportement()
ainsi que la procédure créerComportement
PROCEDURE créerComportement()
RENVOYER allouer cWdClientFinal()
Et je parcours toute ma fenêtre pour trouver le code qui dépend d'un élément externe.
Si par exemple, j'ai le code suivant dans le champ d'initialisation de la fenêtre :
Nom = Client.Nom // Nom est un champ de saisie et Client est un fichier HF SQL
Je peux le remplacer par
Nom = comportement.obtenirNom()
et je crée la méthode obtenirNom
dans la classe cWdClientFinal
:
PROCEDURE obtenirNom()
RENVOYER Client.Nom
Je fais ça pour tous...
]]>Aujourd'hui, cette session a regroupé 3 participants. Nous avons travaillé sur le kata Diamond où l'objectif est de créer un diamant en fonction d'une lettre passée en paramètre.
Par exemple
A : A B : A C : A D : A
B B B B B B
A C C C C
B B D D
A C C
B B
A
Ce kata peut être résolu de plusieurs manières, l'une des méthodes intéressantes étant l'utilisation de PBT (Property Based Testing). Cependant, pour les besoins de la journée, nous avons utilisé du TDD classique. Il se trouve que dans le cas de cet exercice, on passe plus de temps à refactorer qu'à écrire des tests et les faire passer au vert.
Cet exercice a été fait en WinDev 25 avec wxUnit comme framewok de test. Nous avons fait du mob programing (un driver, 2 navigators) avec des cycles de 7 minutes. Tout cela en connexion à distance via Teams.
L'énumération des tests a été assez rapide.
Bien entendu, on ne testera pas les 26 diamants. On espère trouver l'algorithme à partir du 3ème ou 4ème.
Il y a toujours des raccourcis claviers à apprendre. Encore une fois, les participants se sont améliorés au niveau de l'utilisation du clavier.
Nous avons regardé les paramétrages de l'éditeur de code :