90 votes

Comment les tests d'intégration sont-ils écrits pour interagir avec une API externe ?

Tout d'abord, où en sont mes connaissances :

Tests unitaires sont celles qui testent un petit morceau de code (des méthodes uniques, le plus souvent).

Tests d'intégration sont ceux qui testent l'interaction entre plusieurs zones de code (qui, espérons-le, ont déjà leurs propres tests unitaires). Parfois, certaines parties du code à tester exigent que d'autres codes agissent d'une manière particulière. C'est là que les Mocks et les Stubs entrent en jeu. Ainsi, nous simulons/stubons une partie du code pour qu'elle agisse de manière très spécifique. Cela permet à notre test d'intégration de s'exécuter de manière prévisible sans effets secondaires.

Tous les tests doivent pouvoir être exécutés de manière autonome sans partage de données. Si le partage des données est nécessaire, c'est un signe que le système n'est pas assez découplé.

Ensuite, la situation à laquelle je suis confronté :

Lorsque l'on interagit avec une API externe (plus précisément, une API RESTful qui modifiera des données en direct avec une requête POST), je crois savoir que l'on peut (doit ?) simuler l'interaction avec cette API (ce qui est plus éloquent dans la rubrique cette réponse ) pour un test d'intégration. Je comprends également que nous pouvons tester en unité les composants individuels de l'interaction avec cette API (construction de la requête, analyse du résultat, lancement des erreurs, etc.) Ce que je ne comprends pas, c'est comment procéder.

Alors, finalement : Ma ou mes questions.

Comment puis-je tester mon interaction avec une API externe qui a des effets secondaires ?

Un exemple parfait est L'API de contenu de Google pour le shopping . Pour être en mesure d'effectuer la tâche en question, il faut une bonne dose de travail préparatoire, puis effectuer la requête proprement dite, et enfin analyser la valeur de retour. Une partie de ce travail est sans environnement "sandbox". .

Le code pour faire cela a généralement plusieurs couches d'abstraction, quelque chose comme :

<?php
class Request
{
    public function setUrl(..){ /* ... */ }
    public function setData(..){ /* ... */ }
    public function setHeaders(..){ /* ... */ }
    public function execute(..){
        // Do some CURL request or some-such
    }   
    public function wasSuccessful(){
        // some test to see if the CURL request was successful
    }   
}

class GoogleAPIRequest
{
    private $request;
    abstract protected function getUrl();
    abstract protected function getData();

    public function __construct() {
        $this->request = new Request();
        $this->request->setUrl($this->getUrl());
        $this->request->setData($this->getData());
        $this->request->setHeaders($this->getHeaders());
    }   

    public function doRequest() {
        $this->request->execute();
    }   
    public function wasSuccessful() {
        return ($this->request->wasSuccessful() && $this->parseResult());
    }   
    private function parseResult() {
        // return false when result can't be parsed
    }   

    protected function getHeaders() {
        // return some GoogleAPI specific headers
    }   
}

class CreateSubAccountRequest extends GoogleAPIRequest
{
    private $dataObject;

    public function __construct($dataObject) {
        parent::__construct();
        $this->dataObject = $dataObject;
    }   
    protected function getUrl() {
        return "http://...";
    }
    protected function getData() {
        return $this->dataObject->getSomeValue();
    }
}

class aTest
{
    public function testTheRequest() {
        $dataObject = getSomeDataObject(..);
        $request = new CreateSubAccountRequest($dataObject);
        $request->doRequest();
        $this->assertTrue($request->wasSuccessful());
    }
}
?>

Note : Il s'agit d'un exemple PHP5 / PHPUnit.

Étant donné que testTheRequest est la méthode appelée par la suite de tests, l'exemple exécutera une requête en direct.

Maintenant, cette requête en direct va (avec un peu de chance, si tout s'est bien passé) effectuer une requête POST qui a pour effet secondaire de modifier les données en direct.

Est-ce acceptable ? Quelles sont les alternatives possibles ? Je ne vois pas de moyen de simuler l'objet Request pour le test. Et même si je le faisais, cela impliquerait de mettre en place des résultats / points d'entrée pour chaque chemin de code possible que l'API de Google accepte (ce qui, dans ce cas, devrait être trouvé par essais et erreurs), mais me permettrait d'utiliser des fixtures.

Une autre extension se produit lorsque certaines demandes reposent sur le fait que certaines données sont déjà en direct. En reprenant l'exemple de l'API de contenu Google, pour ajouter un flux de données à un sous-compte, ce dernier doit déjà exister.

Une approche à laquelle je pense consiste à suivre les étapes suivantes ;

  1. Sur testCreateAccount
    1. Créer un sous-compte
    2. Affirmer que le sous-compte a été créé
    3. Supprimer le sous-compte
  2. Avoir testCreateDataFeed dépendent de testCreateAccount ne pas avoir d'erreurs
    1. Sur testCreateDataFeed créer un nouveau compte
    2. Créer le flux de données
    3. Affirmer que le flux de données a été créé
    4. Supprimer le flux de données
    5. Supprimer le sous-compte

Cela soulève alors une autre question : comment puis-je tester la suppression des comptes / flux de données ? testCreateDataFeed me semble sale - Que se passe-t-il si la création du flux de données échoue ? Le test échoue, donc le sous-compte n'est jamais supprimé... Je ne peux pas tester la suppression sans la création, alors dois-je écrire un autre test ( testDeleteAccount ) qui s'appuie sur testCreateAccount avant de créer puis de supprimer son propre compte (car les données ne doivent pas être partagées entre les tests).

En résumé

  • Comment puis-je tester l'interaction avec une API externe qui affecte les données en direct ?
  • Comment puis-je simuler/supprimer des objets dans un test d'intégration lorsqu'ils sont cachés derrière des couches d'abstraction ?
  • Que dois-je faire lorsqu'un test échoue et que les données en direct sont laissées dans un état incohérent ?
  • Comment en code Comment dois-je m'y prendre pour faire tout cela ?

En rapport :

0 votes

Il s'agit de plusieurs questions générales, et non d'une question spécifique.

0 votes

28voto

S.Lott Points 207588

Comment puis-je tester l'interaction avec une API externe qui affecte les données en direct ?

Vous ne le faites pas. Vous devez avoir confiance dans le fait que l'API fonctionne réellement.

Vous pouvez - et devez - exercer l'API avec des données réelles pour être sûr de la comprendre.

Mais vous n'avez pas besoin de le tester. Si l'API ne fonctionne pas, arrêtez simplement de l'utiliser. Ne testez pas toutes les limites et tous les cas particuliers.

Comment puis-je simuler/supprimer des objets dans un test d'intégration lorsqu'ils sont cachés derrière des couches d'abstraction ?

C'est le but. Tester l'abstraction. Vous devez avoir confiance que l'implémentation fonctionne. Vous testez votre code. Pas leur code.

Que dois-je faire lorsqu'un test échoue et que les données en direct sont laissées dans un état incohérent ?

Quoi ? Pourquoi testez-vous les API en direct pour être sûr qu'elles fonctionnent ? Vous ne leur faites pas confiance ? Si vous ne leur faites pas confiance, ne les testez pas. Trouvez un fournisseur en qui vous pouvez avoir confiance.

Vous ne testez que votre code. Vous faites confiance leur code. Vous vous moquez trivialement d'une partie de leur code pour être sûr que votre code fonctionne.


Comment faire.

  1. Jouez avec l'API. Envoyez des requêtes. Obtenez des réponses.

  2. Jouez avec votre application. Déterminez les types de demandes que vous allez envoyer.

  3. Retournez à l'API. Envoyez une requête connue. Obtenez la réponse. Sauvegarder cette réponse . C'est votre réponse standard à une bonne demande. Canonisez ceci en un cas de test.

  4. Vous pouvez désormais travailler sur votre application en sachant que vous disposez d'une réponse de qualité supérieure provenant réellement de la véritable API. Cela devrait être suffisant pour commencer à traiter les réponses.

  5. Après avoir travaillé sur quelques cas d'utilisation (bonne demande, mauvaise demande), vous devriez être en mesure d'obtenir une bonne réponse et quelques réponses d'erreur typiques de l'API. Enregistrez les bons messages et les messages d'erreur. Ils sont utiles pour les tests unitaires afin de s'assurer que vous traitez correctement certains types de réponses.

12voto

hakre Points 102271

Il s'agit plutôt d'une réponse supplémentaire à la un déjà donné :

En regardant dans votre code, le class GoogleAPIRequest a une dépendance codée en dur de class Request . Cela vous empêche de la tester indépendamment de la classe de requête, et vous ne pouvez donc pas simuler la requête.

Vous devez rendre la requête injectable, afin de pouvoir la transformer en un simulacre pendant les tests. Cela fait, aucune demande HTTP réelle de l'API n'est envoyée, les données en direct ne sont pas modifiées et vous pouvez tester beaucoup plus rapidement.

2 votes

D'accord à 100%. C'est pourquoi j'ai depuis modifié la conception du code pour permettre cela. Cela dit, il est possible pour class GoogleAPIRequest d'avoir une méthode getNewRequest() qui pourrait être simulé pour retourner un simulacre de Request (comme une des nombreuses alternatives possibles).

1voto

Dizzy Bryan High Points 583

J'ai récemment dû mettre à jour une bibliothèque parce que l'API à laquelle elle se connecte a été mise à jour.

Mes connaissances ne sont pas suffisantes pour expliquer en détail, mais j'ai beaucoup appris en regardant le code. https://github.com/gridiron-guru/FantasyDataAPI

Vous pouvez soumettre une requête à l'API comme vous le feriez normalement, puis enregistrer la réponse dans un fichier json, que vous pouvez ensuite utiliser comme modèle.

Jetez un coup d'œil aux tests de cette bibliothèque qui se connecte à une API en utilisant Guzzle.

Il simule les réponses de l'API, il y a beaucoup d'informations dans la documentation sur la façon dont les tests fonctionnent, cela pourrait vous donner une idée de la façon de procéder.

mais en gros, vous faites un appel manuel à l'API avec tous les paramètres dont vous avez besoin, et vous enregistrez la réponse dans un fichier json.

Lorsque vous écrivez votre test pour l'appel d'api, envoyez les mêmes paramètres et faites en sorte qu'ils soient chargés dans l'objet fictif plutôt que d'utiliser l'api en direct, vous pouvez alors tester que les données dans l'objet fictif que vous avez créé contiennent les valeurs attendues.

Ma version mise à jour de l'api en question peut être trouvée ici. Repo mis à jour

1voto

dragon788 Points 1102

L'une des façons de tester les API externes est, comme vous l'avez mentionné, de créer un simulacre et de travailler avec le comportement codé en dur tel que vous l'avez compris.

On parle parfois de ce type de test comme d'un test "basé sur un contrat", où vous pouvez écrire des tests contre l'API en fonction du comportement que vous avez observé et codé, et lorsque ces tests commencent à échouer, le "contrat est rompu". S'il s'agit de tests REST simples utilisant des données factices, vous pouvez également les fournir au fournisseur externe pour qu'il les exécute afin qu'il puisse découvrir où/quand il modifie suffisamment l'API pour qu'il s'agisse d'une nouvelle version ou qu'il produise un avertissement de non-compatibilité.

Réf : https://www.thoughtworks.com/radar/techniques/consumer-driven-contract-testing

0voto

Reza Sanaie Points 2930

Construire par-dessus ce que dit la réponse la plus votée.... Voici comment je l'ai fait et cela fonctionne très bien.

  1. Création d'un objet curl fantaisie
  2. Indiquez au simulateur les paramètres qu'il attend.
  3. Simulez ce que sera la réponse de l'appel curl dans votre fonction.
  4. Laissez votre code faire son travail

    $curlMock = $this->getMockBuilder('\Curl\Curl')
                     ->setMethods(['get'])
                     ->getMock();
    
    $curlMock
        ->expects($this->once())
        ->method('get')
        ->with($URL .  '/users/' . urlencode($userId));
    
    $rawResponse = <<<EOL
    {
         "success": true,
         "result": {
         ....
         }
    }
    EOL;
    
    $curlMock->rawResponse = $rawResponse;
    $curlMock->error = null;
    
    $apiService->curl = $curlMock;
    
    // call the function that inherently consumes the API via curl
    $result = $apiService->getUser($userId);
    
    $this->assertTrue($result);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X