J'apprends Go en codant un petit projet personnel. Même si c'est petit, j'ai décidé de faire des tests unitaires rigoureux pour apprendre de bonnes habitudes sur Go dès le début.
Les tests unitaires triviaux étaient tous très bien, mais je suis maintenant perplexe avec les dépendances; Je veux pouvoir remplacer certains appels de fonction par des faux. Voici un extrait de mon code:
func get_page(url string) string {
get_dl_slot(url)
defer free_dl_slot(url)
resp, err := http.Get(url)
if err != nil { return "" }
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil { return "" }
return string(contents)
}
func downloader() {
dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
content := get_page(BASE_URL)
links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
matches := links_regexp.FindAllStringSubmatch(content, -1)
for _, match := range matches{
go serie_dl(match[1], match[2])
}
}
J'aimerais pouvoir tester downloader () sans réellement obtenir une page via http - c'est-à-dire en se moquant soit de get_page (plus facile car il ne renvoie que le contenu de la page sous forme de chaîne) soit de http.Get ().
J'ai trouvé ce fil: https://groups.google.com/forum/#!topic/golang-nuts/6AN1E2CJOxI qui semble concerner un problème similaire. Julian Phillips présente sa bibliothèque, Withmock ( http://github.com/qur/withmock ) comme une solution, mais je ne parviens pas à la faire fonctionner. Voici les parties pertinentes de mon code de test, qui est en grande partie un code culte pour moi, pour être honnête:
import (
"testing"
"net/http" // mock
"code.google.com/p/gomock"
)
...
func TestDownloader (t *testing.T) {
ctrl := gomock.NewController()
defer ctrl.Finish()
http.MOCK().SetController(ctrl)
http.EXPECT().Get(BASE_URL)
downloader()
// The rest to be written
}
La sortie de test est la suivante:
ERROR: Failed to install '_et/http': exit status 1
output:
can't load package: package _et/http: found packages http (chunked.go) and main (main_mock.go) in /var/folders/z9/ql_yn5h550s6shtb9c5sggj40000gn/T/withmock570825607/path/src/_et/http
Le Withmock est-il une solution à mon problème de test? Que dois-je faire pour le faire fonctionner?
la source
Réponses:
Bravo à vous pour avoir pratiqué de bons tests! :)
Personnellement, je n'utilise pas
gomock
(ni aucun framework moqueur d'ailleurs; se moquer dans Go est très facile sans cela). Je passerais une dépendance à ladownloader()
fonction en tant que paramètre, ou je créeraisdownloader()
une méthode sur un type, et le type peut contenir laget_page
dépendance:Méthode 1: passer
get_page()
comme paramètre dedownloader()
Principale:
Tester:
Méthode 2: créer
download()
une méthode d'un typeDownloader
:Si vous ne souhaitez pas passer la dépendance en tant que paramètre, vous pouvez également créer
get_page()
un membre d'un type et créerdownload()
une méthode de ce type, qui peut ensuite utiliserget_page
:Principale:
Tester:
la source
Si vous modifiez la définition de votre fonction pour utiliser une variable à la place:
Vous pouvez le remplacer dans vos tests:
Attention cependant, vos autres tests peuvent échouer s'ils testent la fonctionnalité de la fonction que vous remplacez!
Les auteurs de Go utilisent ce modèle dans la bibliothèque standard Go pour insérer des crochets de test dans le code afin de faciliter les tests:
https://golang.org/src/net/hook.go
https://golang.org/src/net/dial.go#L248
https://golang.org/src/net/dial_test.go#L701
la source
J'utilise une approche légèrement différente où les méthodes de struct public implémentent des interfaces mais leur logique se limite à simplement envelopper les fonctions privées (non exportées) qui prennent ces interfaces comme paramètres. Cela vous donne la granularité dont vous auriez besoin pour vous moquer de pratiquement toutes les dépendances tout en disposant d'une API propre à utiliser depuis l'extérieur de votre suite de tests.
Pour comprendre cela, il est impératif de comprendre que vous avez accès aux méthodes non exportées dans votre cas de test (c'est-à-dire à partir de vos
_test.go
fichiers) afin que vous les testiez au lieu de tester celles exportées qui n'ont pas de logique à l'intérieur de l'emballage.Pour résumer: testez les fonctions non exportées au lieu de tester celles exportées!
Faisons un exemple. Supposons que nous ayons une structure d'API Slack qui a deux méthodes:
SendMessage
méthode qui envoie une requête HTTP à un webhook SlackSendDataSynchronously
méthode qui a donné une tranche de chaînes les itère et appelleSendMessage
à chaque itérationDonc, pour tester
SendDataSynchronously
sans faire de requête HTTP à chaque fois, il faudrait se moquerSendMessage
, non?Ce que j'aime dans cette approche, c'est qu'en regardant les méthodes non exportées, vous pouvez clairement voir quelles sont les dépendances. En même temps, l'API que vous exportez est beaucoup plus propre et avec moins de paramètres à transmettre puisque la vraie dépendance ici est juste le récepteur parent qui implémente toutes ces interfaces lui-même. Pourtant, chaque fonction ne dépend potentiellement que d'une partie de celle-ci (une, peut-être deux interfaces), ce qui rend les refacteurs beaucoup plus faciles. C'est bien de voir comment votre code est vraiment couplé rien qu'en regardant les signatures des fonctions, je pense que cela fait un outil puissant contre l'odeur de code.
Pour simplifier les choses, je mets tout dans un seul fichier pour vous permettre d'exécuter le code dans le terrain de jeu ici mais je vous suggère également de consulter l'exemple complet sur GitHub, voici le fichier slack.go et ici le slack_test.go .
Et voici le tout :)
la source
Je ferais quelque chose comme,
Principale
Tester
Et
_
j'éviterais en golang. Mieux vaut utiliser camelCasela source
p := patch(mockGetPage, getPage); defer p.done()
. Je suis nouveau, et j'essayais de le faire en utilisant launsafe
bibliothèque, mais cela semble impossible à faire dans le cas général.Avertissement: cela peut augmenter un peu la taille du fichier exécutable et coûter un peu en termes de performances d'exécution. OMI, ce serait mieux si golang avait une fonctionnalité telle que le décorateur de macro ou de fonction.
Si vous souhaitez simuler des fonctions sans changer son API, le moyen le plus simple est de modifier un peu l'implémentation:
De cette façon, nous pouvons réellement simuler une fonction parmi les autres. Pour plus de commodité, nous pouvons fournir un tel passe-partout moqueur:
Dans le fichier de test:
la source
Étant donné que le test unitaire est le domaine de cette question, nous vous recommandons vivement d'utiliser https://github.com/bouk/monkey . Ce package vous permet de simuler un test sans changer votre code source d'origine. Comparé à une autre réponse, c'est plus "non intrusif"。
PRINCIPALE
TEST DE SIMULATION
Le mauvais côté est:
- Rappelé par Dave.C, cette méthode n'est pas sûre. Ne l'utilisez donc pas en dehors du test unitaire.
- Est-ce que Go est non idiomatique.
Le bon côté est:
++ Est non intrusif. Vous faire faire les choses sans changer le code principal. Comme l'a dit Thomas.
++ Vous faire changer le comportement du package (peut-être fourni par un tiers) avec le moins de code.
la source