Outils pour utilisateurs

Outils du site


dev:go:tutoriels:ecrire_un_module_en_go

Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentesRévision précédente
Prochaine révision
Révision précédente
dev:go:tutoriels:ecrire_un_module_en_go [2023/08/04 14:51] yoanndev:go:tutoriels:ecrire_un_module_en_go [2023/08/06 10:13] (Version actuelle) yoann
Ligne 1: Ligne 1:
 {{tag>dev module tutoriel go}} {{tag>dev module tutoriel go}}
- 
-:TODO: 
  
 ====== Écrire un module en Go ====== ====== Écrire un module en Go ======
Ligne 305: Ligne 303:
 Hello, Yoann. Bienvenue! Hello, Yoann. Bienvenue!
 </code> </code>
 +
 +===== Retourner un message à plusieurs personnes =====
 +
 +Nous allons modifier une dernière fois nos modules afin d'obtenir des messages pour plusieurs personnes en une seule requête. Pour cela, nous allons devoir gérer plusieurs valeurs en entrée, associer à chacune une valeur et produire en sortie à valeurs multiples. En entrée on devra passer un ensemble de noms à la fonction qui retournera pour chacun des noms un message de bienvenue.
 +
 +Ces modifications soulèvent une problématique : modifier les paramètres de la fonction Hello() change sa signature. Si le module a déjà été publié et qu'il est utilisé, ces changements conduiront à une à des erreurs dans les programmes appelants.
 +
 +Dans ce cas une meilleur option est d'écrire une nouvelle fonction avec un nom différent. Cette nouvelle fonction prendra plusieurs paramètres. Cela permet de préserver l'ancienne fonction et assure la rétrocompatibilité.
 +
 +Via l'éditeur, on modifie notre fichier greetings/greetings.go
 +<code go greetings.go>
 +package greetings
 +
 +import (
 + "errors"
 + "fmt"
 + "math/rand"
 +)
 +
 +// Hello retourne un message de bienvenue à la personne désignée
 +func Hello(name string) (string, error) {
 + //Si aucun nom n'est donné retourne une erreur avec un message.
 + if name == "" {
 + return "", errors.New("empty name")
 + }
 +
 + message := fmt.Sprintf(randomFormat(), name)
 + return message, nil
 +}
 +
 +// HelloAll retourne un map associant un message de bienvenue à chaque personne en entrée
 +func HelloAll(names []string) (map[string]string, error) {
 + //un map pour associer un message à chaque nom
 + messages := make(map[string]string)
 +
 + //boucle sur la slice en entrée (les noms) et appelle Hello() pour associer une message
 + for _, name := range names {
 + //pour chaque index(non utilisé _), valeur du paramètre 'names'
 + msg, err := Hello(name)
 +
 + if err != nil {
 + //une erreur s'est produite, on retourne un objet vide et le code erreur
 + return nil, err
 + }
 +
 + messages[name] = msg
 + }
 +
 + return messages, nil
 +}
 +
 +// randomFormat retourne aléatoirement un message préformatté parmi l'ensemble des messages disponibles.
 +func randomFormat() string {
 + formats := []string{
 + "Hello, %v. Bienvenue!",
 + "Heureux de vous revoir %v!",
 + "Salut %v, enchanté!",
 + }
 +
 + //retourne aléatoirement un format de message
 + return formats[rand.Intn(len(formats))]
 +}
 +</code> 
 +
 +Dans ce code nous avons:
 +  * Ajouté une fonction HelloAll() ayant en paramètre une slice de plusieurs noms et retournant un map permettant d'associer un message (valeur) à chaque nom (clé) du map.
 +  * La nouvelle fonction HelloAll() appelle la fonction Hello(). Cet usage des fonction est aussi désigné factorisation et réduit les risques d'erreurs liés à la duplication de code.
 +  * Créé une variable "messages" de type map pour associer chaque message retourné par Hello() comme valeur à un clé (le nom passé en paramètre). En Go on initialise un map avec la syntaxe **''make(map[key-type]value-type)''**. La fonction HelloAll() retourne le map à l'appelant.
 +  * Exécuter une boucle sur chaque élément de la slice "names". Dans cette boucle **''for''** l'opérateur **''range''** retourne deux valeurs : l'index de l'élément courant dans la boucle et une copie de sa valeur. N'ayant pas besoin de l'index il a été affecté à l’[[https://go.dev/doc/effective_go.html#blank|identifiant spécial '_']] pour être ignoré.  
 +
 +Dans notre fichier hello.go nous pouvons à présent faire quelques changements :
 +
 +<code go hello.go>
 +package main
 +
 +import (
 + "fmt"
 + "log"
 +
 + "example.com/greetings"
 +)
 +
 +func main() {
 + //Définition de propriétés du Logger
 + log.Default().SetPrefix("greetings: ")
 + //Désactive l'afficahge du timestamp, du fichier source et du numero
 + //de ligne
 + log.SetFlags((0))
 +
 + //définition d'une slice contenant les noms
 + names := []string{"Yoann", "Alfred", "Emilie", "John"}
 +
 + //Obtenir un message de bienvenue pour chaque nom
 + messages, err := greetings.HelloAll(names)
 +
 + //Si une erreur est retournée, elle est affichée dans la console
 + //et le programme s'arrête
 + if err != nil {
 + log.Fatal(err)
 + }
 +
 + //Affiche le message dans la console
 + fmt.Println(messages)
 +}
 +</code>
 +
 +Les changements sont les suivants :
 +  * Création d'une variable "names" de type slice contenant 4 noms ;
 +  * Passage en argument de la variable "names" à la fonction HelloAll().
 +
 +Exécutons le code ainsi modifié :
 +<code bash>
 +go run .
 +map[Alfred:Heureux de vous revoir Alfred! Emilie:Hello, Emilie. Bienvenue! John:Heureux de vous revoir John! Yoann:Salut Yoann, enchanté!]
 +</code>
 +
 +===== Ajout d' un test unitaire =====
 +
 +Maintenant que notre code a implémenter les fonctionnalités souhaitées, ajoutons un test. Tester le code pendant le développement peut aider à révéler les bugs introduits lors des différentes modifications. Ici nous allons ajouter un test pour la fonction 'Hello()'.
 +
 +Go intègre le support des tests unitaires. Le paquetage "testing" de Go et la commande test s'appuient sur des conventions de nommage et permettent d'écrire et d'exécuter facilement et rapidement des tests. 
 +
 +  * Dans le répertoire greetings, créer un fichier ''greetings_test.go''. Terminer une fichier par le suffixe "_test.go" indique à la **commande test** de go que ce fichier contient des fonctions de test ;
 +  * Éditer le fichier greetings_test.go avec le contenu suivant :
 +
 +<code go greetings_test.go>
 +package greetings
 +
 +import (
 + "regexp"
 + "testing"
 +)
 +
 +// TestHelloName appelle Hello() avec un nom et vérifie
 +// la valeur retournée.
 +func TestHelloName(t *testing.T) {
 + aName := "Albert"
 + want := regexp.MustCompile(`\b` + aName + `\b`)
 + msg, err := Hello("Albert")
 +
 + if !want.MatchString(msg) || err != nil {
 + t.Fatalf(`Hello("Albert") = %q, %v, want match for %#q, nil`, msg, err, want)
 + }
 +}
 +
 +// TestHelloEmpty appelle Helo() avec une chaine vide doit
 +// retourner une erreur.
 +func TestHelloEmpty(t *testing.T) {
 + msg, err := Hello("")
 +
 + if msg != "" || err == nil {
 + t.Fatalf(`Hello("") = %q, %v, want "", error`, msg, err)
 + }
 +}
 +</code>
 +
 +Dans ce code on a :
 +  * Définit des fonctions de test dans le même paquetage que le code à tester ;
 +  * Définit deux fonctions de test pour tester la fonction greetings.Hello(). Les fonctions de test doivent avoir un nom préfixé par 'Test'. Les fonctions de test doivent en argument un pointeur vers le paquetage testing. Les méthodes de ce paramètre sont utilisées pour générer les rapports et les logs du test ;
 +  * A propos des deux tests:
 +    * TestHelloName() appelle la fonction Hello() avec un nom en argument et vérifie que la fonction est capable de retourner un message valide. Si le retour est une erreur ou un message avec un contenu non attendu on utilise le paramètre "t" et sa méthode "Fatalf()" pour afficher un message dans la console et interrompre l'exécution.
 +    * TestHelloEmpty() appelle la fonction Hello() avec une chaine vide. Ce test permet de valider le fonctionnement de la gestion des erreurs. Si le test retourne une chaine non vide ou ne retourne pas d'erreur on utilise le paramètre "t" et la méthode "Fatalf()" pour afficher un message et interrompre l'exécution des tests.
 +
 +Depuis la ligne de commande on peut lancer les tests:
 +<code powershell>
 +go test
 +</code>
 +
 +La commande **go test** exécute les fonctions de test (ayant des noms préfixés par "Test") dans les fichiers  *_test.go. On peut ajouter l'argument -v (verbeux) pour avoir un retour plus détaillé de l'exécution des tests.
 +
 +Si on introduit (ici volontairement) une erreur lors du developpement en modifiant le comportement de la fonction Hello(), une des fonctions de test pourra révèler l'anomalie:
 +
 +<code>
 + go test -v
 +=== RUN   TestHelloName
 +    greetings_test.go:16: Hello("Albert") = "Heureux de vous revoir %!v(MISSING)!", <nil>, want match for `\bAlbert\b`, nil
 +--- FAIL: TestHelloName (0.00s)
 +=== RUN   TestHelloEmpty
 +--- PASS: TestHelloEmpty (0.00s)
 +FAIL
 +exit status 1
 +FAIL    example.com/greetings   0.140s
 +</code>
 +
 +===== Compiler et installer l'application =====
 +
 +Ici on aborde quelques nouvelles commandes Go. La commande go run permet de rapidement compiler et exécuter un programme lors du développement alors qu'on effectue régulièrement des modifications.
 +
 +  * La commande **''go build''** compile les paquetages et les dépendances, produit un binaire mais n'installe pas le résultat ;
 +  * La commande go **''install''** compile et installe le binaire.
 +
 +Lorsque le binaire est créé avec **''go build''** il peut être exécuté seulement depuis le répertoire courant ou si l'on précise son chemin complet (absolu). Installer l’exécutable consiste à placer le binaire dans un répertoire spécifique afin de pouvoir l'invoquer sans préciser son chemin complet.
 +
 +Pour connaitre le chemin du dossier d'installation utlisé par Go:
 +<code>
 +go list -f '{{.Target}}'
 +</code> 
 +
 +Vérifier que le dossier d'installation de Go est présent dans le PATH :
 +
 +<code powershell>
 +echo $env:PATH
 +</code>
 +
 +La variable PATH doit contenir le dossier d'installation de Go. Si ce n'est pas le cas modifier le PATH de l'utilisateur.
 +
 +Si vous souhaitez installer le binaire dans un répertoire différent de votre PATH, vous pouvez modifier/définir la valeur de la variable **''GOBIN''**
 +
 +<code>
 +go env -w GOBIN=C:\path\to\your\bin
 +</code>
 +
 +Une fois le chemin d'installation identifier/redéfinit vous pouvez invoquer la commande ''**go install**''.
  
  
dev/go/tutoriels/ecrire_un_module_en_go.1691160677.txt.gz · Dernière modification : 2023/08/04 14:51 de yoann