For the complete documentation index, see llms.txt. This page is also available as Markdown.

OS Exec

Vous pouvez trouver tout le code ici

keith6014 demande sur reddit

J'exécute une commande en utilisant os/exec.Command() qui génère des données XML. La commande sera exécutée dans une fonction appelée GetData().

Pour tester GetData(), j'ai créé des données de test.

Dans mon fichier _test.go, j'ai un TestGetData qui appelle GetData() mais cela utilisera os.exec, alors que j'aimerais plutôt utiliser mes données de test.

Quelle est une bonne façon d'y parvenir ? En appelant GetData, devrais-je avoir un mode "test" pour qu'il lise un fichier, par exemple GetData(mode string) ?

Quelques observations :

  • Quand quelque chose est difficile à tester, c'est souvent parce que la séparation des préoccupations n'est pas tout à fait correcte

  • N'ajoutez pas de "modes de test" dans votre code, utilisez plutôt l'Injection de Dépendances afin de pouvoir modéliser vos dépendances et séparer les préoccupations.

J'ai pris la liberté de deviner à quoi pourrait ressembler le code

type Payload struct {
	Message string `xml:"message"`
}

func GetData() string {
	cmd := exec.Command("cat", "msg.xml")

	out, _ := cmd.StdoutPipe()
	var payload Payload
	decoder := xml.NewDecoder(out)

	// ces 3 fonctions peuvent renvoyer des erreurs mais je les ignore par souci de concision
	cmd.Start()
	decoder.Decode(&payload)
	cmd.Wait()

	return strings.ToUpper(payload.Message)
}
  • Il utilise exec.Command qui vous permet d'exécuter une commande externe au processus

  • Nous capturons la sortie dans cmd.StdoutPipe qui nous renvoie un io.ReadCloser (cela est important pour plus tard)

  • Le reste du code est plus ou moins copié-collé de la documentation excellente.

    • Nous capturons toute sortie de stdout dans un io.ReadCloser, puis nous Start (démarrons) la commande et attendons que toutes les données soient lues en appelant Wait. Entre ces deux appels, nous décodons les données dans notre structure Payload.

Voici ce que contient msg.xml

J'ai écrit un test simple pour montrer son fonctionnement

Code testable

Un code testable est découplé et à but unique. Pour moi, il semble y avoir deux préoccupations principales pour ce code :

  1. Récupérer les données XML brutes

  2. Décoder les données XML et appliquer notre logique métier (dans ce cas strings.ToUpper sur le <message>)

La première partie consiste simplement à copier l'exemple de la bibliothèque standard.

La deuxième partie est celle où se trouve notre logique métier et en regardant le code, nous pouvons voir où commence la "couture" dans notre logique : c'est là où nous obtenons notre io.ReadCloser. Nous pouvons utiliser cette abstraction existante pour séparer les préoccupations et rendre notre code testable.

Le problème avec GetData est que la logique métier est couplée avec le moyen d'obtenir le XML. Pour améliorer notre conception, nous devons les découpler

Notre TestGetData peut servir de test d'intégration entre nos deux préoccupations, donc nous le conserverons pour nous assurer que tout continue à fonctionner.

Voici à quoi ressemble le code nouvellement séparé

Maintenant que GetData prend son entrée à partir d'un simple io.Reader, nous l'avons rendu testable et il ne se préoccupe plus de la façon dont les données sont récupérées ; les gens peuvent réutiliser la fonction avec n'importe quoi qui renvoie un io.Reader (ce qui est extrêmement courant). Par exemple, nous pourrions commencer à récupérer le XML depuis une URL au lieu de la ligne de commande.

Voici un exemple de test unitaire pour GetData.

En séparant les préoccupations et en utilisant les abstractions existantes dans Go, tester notre logique métier importante devient un jeu d'enfant.

Mis à jour