OS Exec
Vous pouvez trouver tout le code ici
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.Commandqui vous permet d'exécuter une commande externe au processusNous capturons la sortie dans
cmd.StdoutPipequi nous renvoie unio.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 nousStart(démarrons) la commande et attendons que toutes les données soient lues en appelantWait. Entre ces deux appels, nous décodons les données dans notre structurePayload.
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 :
Récupérer les données XML brutes
Décoder les données XML et appliquer notre logique métier (dans ce cas
strings.ToUppersur 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