En tant qu'ingénieur logiciel, vous êtes probablement habitué à écrire du code pour interagir avec des services HTTP externes. Après tout, c’est l’une des choses les plus courantes que nous faisons ! Qu'il s'agisse de récupérer des données, de traiter des paiements avec un fournisseur ou d'automatiser les publications sur les réseaux sociaux, nos applications impliquent presque toujours des requêtes HTTP externes. Pour que notre logiciel soit fiable et maintenable, nous avons besoin d'un moyen de tester le code chargé d'exécuter ces requêtes et de gérer les erreurs qui pourraient survenir. Cela nous laisse quelques options :
Ces options ne sont pas terribles, surtout si elles peuvent toutes être utilisées ensemble, mais nous avons une meilleure option : le test magnétoscope.
Les tests VCR, du nom du magnétoscope, sont un type de test simulé qui génère des montages de test à partir de demandes réelles. Les appareils enregistrent la demande et la réponse pour les réutiliser automatiquement dans les tests futurs. Bien que vous deviez peut-être modifier les appareils par la suite pour gérer des entrées dynamiques basées sur le temps ou supprimer des informations d'identification, c'est beaucoup plus simple que de créer des simulations à partir de zéro. Les tests VCR présentent quelques avantages supplémentaires :
Maintenant que vous voyez la motivation derrière les tests VCR, examinons plus en détail comment l'implémenter dans Go à l'aide de dnaeon/go-vcr.
Cette bibliothèque s'intègre de manière transparente dans n'importe quel code client HTTP. Si le code de votre bibliothèque client ne permet pas déjà de définir le *http.Client ou le http.Transport du client, vous devez l'ajouter maintenant.
Pour ceux qui ne sont pas familiers, un http.Transport est une implémentation de http.RoundTripper, qui est essentiellement un middleware côté client qui peut accéder à la requête/réponse. Il est utile pour implémenter des tentatives automatiques sur des réponses de niveau 500 ou 429 (limite de débit), ou pour ajouter des métriques et journaliser les requêtes. Dans ce cas, il permet à go-vcr de rediriger les requêtes vers son propre serveur HTTP en cours.
Commençons par un exemple simple. Nous souhaitons créer un package qui envoie des requêtes à l'API gratuite https://cleanuri.com. Ce package fournira une fonction : Shorten(string) (string, error)
Comme il s'agit d'une API gratuite, peut-être pouvons-nous simplement la tester en faisant des requêtes directement au serveur ? Cela pourrait fonctionner, mais peut entraîner quelques problèmes :
Ok, et si on créait une interface et qu'on s'en moquait ? Notre package est incroyablement simple, cela le compliquerait donc trop. Puisque le niveau le plus bas que nous utilisons est *http.Client, nous devrons définir une nouvelle interface autour de celui-ci et implémenter une simulation.
Une autre option consiste à remplacer l'URL cible pour utiliser un port local servi par httptest.Server. Il s'agit essentiellement d'une version simplifiée de ce que fait go-vcr et serait suffisante dans notre cas simple, mais ne sera pas maintenable dans des scénarios plus complexes. Même dans cet exemple, vous verrez à quel point la gestion des appareils générés est plus facile que la gestion de différentes implémentations de serveurs fictifs.
Étant donné que notre interface est déjà définie et que nous connaissons des entrées/sorties valides en essayant l'interface utilisateur sur https://cleanuri.com, c'est une excellente opportunité de pratiquer le développement piloté par les tests. Nous allons commencer par implémenter un test simple pour notre fonction Shorten :
package shortener_test func TestShorten(t *testing.T) { shortened, err := shortener.Shorten("https://dev.to/calvinmclean") if err != nil { t.Errorf("unexpected error: %v", err) } if shortened != "https://cleanuri.com/7nPmQk" { t.Errorf("unexpected result: %v", shortened) } }
Assez facile ! Nous savons que la compilation du test échouera car shortener.Shorten n'est pas défini, mais nous l'exécutons quand même, donc le corriger sera plus satisfaisant.
Enfin, allons-y et implémentons cette fonction :
package shortener var DefaultClient = http.DefaultClient const address = "https://cleanuri.com/api/v1/shorten" // Shorten will returned the shortened URL func Shorten(targetURL string) (string, error) { resp, err := DefaultClient.PostForm( address, url.Values{"url": []string{targetURL}}, ) if err != nil { return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return "", fmt.Errorf("unexpected response code: %d", resp.StatusCode) } var respData struct { ResultURL string `json:"result_url"` } err = json.NewDecoder(resp.Body).Decode(&respData) if err != nil { return "", err } return respData.ResultURL, nil }
Maintenant, notre test réussit ! C'est aussi satisfaisant que je l'ai promis.
Pour commencer à utiliser VCR, nous devons initialiser l'enregistreur et remplacer shortener.DefaultClient au début du test :
func TestShorten(t *testing.T) { r, err := recorder.New("fixtures/dev.to") if err != nil { t.Fatal(err) } defer func() { require.NoError(t, r.Stop()) }() if r.Mode() != recorder.ModeRecordOnce { t.Fatal("Recorder should be in ModeRecordOnce") } shortener.DefaultClient = r.GetDefaultClient() // ...
Exécutez le test pour générer luminaires/dev.to.yaml avec des détails sur la demande et la réponse du test. Lorsque nous réexécutons le test, il utilise la réponse enregistrée au lieu de contacter le serveur. Ne vous contentez pas de me croire sur parole ; éteignez le WiFi de votre ordinateur et relancez les tests !
Vous remarquerez peut-être également que le temps nécessaire pour exécuter le test est relativement cohérent puisque go-vcr enregistre et rejoue la durée de la réponse. Vous pouvez modifier manuellement ce champ dans le YAML pour accélérer les tests.
Pour démontrer davantage les avantages de ce type de test, ajoutons une autre fonctionnalité : réessayez après 429 réponses en raison de la limitation du débit. Puisque nous savons que la limite de débit de l'API est par seconde, Shorten peut automatiquement attendre une seconde et réessayer s'il reçoit un code de réponse 429.
J'ai essayé de reproduire cette erreur en utilisant directement l'API, mais il semble qu'elle réponde avec les URL existantes d'un cache avant de prendre en compte la limite de débit. Plutôt que de polluer le cache avec de fausses URL, nous pouvons cette fois créer nos propres simulations.
C'est un processus simple puisque nous avons déjà généré des luminaires. Après avoir copié/collé luminaires/dev.to.yaml dans un nouveau fichier, dupliquez l'interaction requête/réponse réussie et modifiez le code de la première réponse de 200 à 429. Cet appareil imite une nouvelle tentative réussie après un échec de limitation de débit.
La seule différence entre ce test et le test original est le nouveau nom de fichier du luminaire. Le résultat attendu est le même puisque Shorten doit gérer l’erreur. Cela signifie que nous pouvons lancer le test en boucle pour le rendre plus dynamique :
func TestShorten(t *testing.T) { fixtures := []string{ "fixtures/dev.to", "fixtures/rate_limit", } for _, fixture := range fixtures { t.Run(fixture, func(t *testing.T) { r, err := recorder.New(fixture) if err != nil { t.Fatal(err) } defer func() { require.NoError(t, r.Stop()) }() if r.Mode() != recorder.ModeRecordOnce { t.Fatal("Recorder should be in ModeRecordOnce") } shortener.DefaultClient = r.GetDefaultClient() shortened, err := shortener.Shorten("https://dev.to/calvinmclean") if err != nil { t.Errorf("unexpected error: %v", err) } if shortened != "https://cleanuri.com/7nPmQk" { t.Errorf("unexpected result: %v", shortened) } }) } }
Encore une fois, le nouveau test échoue. Cette fois, en raison de la réponse 429 non gérée, implémentons donc la nouvelle fonctionnalité pour réussir le test. Afin de maintenir la simplicité, notre fonction gère l'erreur en utilisant time.Sleep et un appel récursif plutôt que de gérer la complexité de la prise en compte du nombre maximal de tentatives et des interruptions exponentielles :
func Shorten(targetURL string) (string, error) { // ... switch resp.StatusCode { case http.StatusOK: case http.StatusTooManyRequests: time.Sleep(time.Second) return Shorten(targetURL) default: return "", fmt.Errorf("unexpected response code: %d", resp.StatusCode) } // ...
Maintenant, relancez les tests et voyez-les réussir !
Allez plus loin par vous-même et essayez d'ajouter un test pour une mauvaise demande, qui se produira lors de l'utilisation d'une URL non valide comme ma-fausse-url.
Le code complet de cet exemple (et le test de mauvaise requête) est disponible sur Github.
Les avantages des tests VCR ressortent clairement de cet exemple simple, mais ils sont encore plus efficaces lorsqu'il s'agit d'applications complexes où les requêtes et les réponses sont lourdes. Plutôt que de faire face à des simulations fastidieuses ou d'opter pour aucun test du tout, je vous encourage à essayer cela dans vos propres applications. Si vous vous appuyez déjà sur des tests d'intégration, démarrer avec VCR est encore plus simple puisque vous avez déjà de vraies requêtes pouvant générer des luminaires.
Découvrez plus de documentation et d'exemples dans le référentiel Github du package : https://github.com/dnaeon/go-vcr
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!