web-dev-qa-db-fra.com

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

D'abord, là où sont mes connaissances:

Les tests unitaires sont ceux qui testent un petit morceau de code (une seule méthode, principalement). 

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, des parties du code sous test nécessitent qu'un autre code agisse de manière particulière. C’est là que Mocks & Stubs entrent en jeu. Nous modifions donc une partie du code pour qu’elle soit très performante. Cela permet à notre test d'intégration de fonctionner 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 suffisamment découplé.

Ensuite, la situation à laquelle je suis confronté:

Lors de l'interaction avec une API externe (en particulier une API RESTful qui modifiera les données en temps réel avec une demande POST), je comprends que nous pouvons (devrions?) Simuler l'interaction avec cette API (plus éloquemment déclaré dans this répondre ) pour un test d'intégration. Je comprends également que nous pouvons tester unitairement les composants individuels d’interaction avec cette API (construction de la requête, analyse du résultat, erreurs de projection, etc.). Ce que je ne comprends pas, c'est comment s'y prendre.

Donc, finalement: ma (mes) question (s).

Comment tester mon interaction avec une API externe ayant des effets secondaires?

Un exemple parfait est l'API de contenu de Google pour les achats . Pour pouvoir effectuer la tâche à accomplir, il faut un certain travail de préparation, puis exécuter la demande, puis analyser la valeur de retour. Une partie de ceci est sans aucun 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: Ceci est 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.

Désormais, cette requête en direct fera (espérons-le, à condition que tout se passe bien) de faire une requête POST ayant pour effet secondaire de modifier les données en direct.

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

Une autre extension est lorsque certaines demandes s'appuient sur certaines données déjà en direct. En reprenant l'exemple de l'API de contenu Google, pour ajouter un flux de données à un compte secondaire, ce dernier doit déjà exister.

Une approche à laquelle je peux penser est la suivante:

  1. Dans testCreateAccount
    1. Créer un sous-compte
    2. Assert le sous-compte a été créé
    3. Supprimer le sous-compte
  2. Avez-testCreateDataFeed dépendre de testCreateAccount ne pas avoir d'erreur
    1. Dans testCreateDataFeed, créez un nouveau compte
    2. Créer le flux de données
    3. Assert le flux de données a été créé
    4. Supprimer le flux de données
    5. Supprimer le sous-compte

Cela soulève ensuite la question suivante: Comment tester la suppression de comptes/flux de données? testCreateDataFeed me semble sale - Et si la création du flux de données échouait? Le test échoue, le sous-compte n’est jamais supprimé. Je ne peux pas tester la suppression sans la créer. Par conséquent, j’écris un autre test (testDeleteAccount) qui repose sur testCreateAccount avant de créer puis de supprimer son propre compte (car data ne pas être partagé entre les tests).

En résumé

  • Comment tester l'interaction avec une API externe qui affecte les données en temps réel?
  • Comment puis-je simuler/remplacer 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 restent incohérentes?
  • Comment dans le code dois-je réellement faire tout cela?

En relation:

66
Jess Telford

Comment tester l'interaction avec une API externe qui affecte les données en temps réel?

Vous pas. Vous devez avoir la certitude que l'API fonctionne réellement.

Vous pouvez - et devriez - utiliser l’API avec des données réelles pour vous assurer de bien 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 tous les cas Edge et Corner.

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

C'est le but. Testez l'abstraction. Vous devez avoir confiance que la mise en œuvre fonctionne. Vous testez votre code. Pas leur code.

Que dois-je faire lorsqu'un test échoue et que les données en direct restent incohérentes?

Quoi? Pourquoi testez-vous les API en direct pour vous assurer qu'elles fonctionnent? Vous ne leur faites pas confiance? Si vous ne leur faites pas confiance, ne testez pas. Trouvez un fournisseur de confiance.

Vous testez seulement votre code. Vous avez confiance leur code. Vous vous moquez assez de leur code pour vous assurer que votre code fonctionne.


Comment fais tu ça.

  1. Jouez avec l'API. Envoyer des demandes. Obtenez des réponses.

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

  3. Retournez à l'API. Envoyez une bonne demande connue. Obtenez la réponse. Enregistrer cette réponse . C’est votre réponse idéale à une bonne demande. Canoniser cela dans un cas de test. 

  4. Maintenant, vous pouvez travailler sur votre application en sachant que vous avez une réponse standard qui vient de la vraie API. Cela devrait être suffisant pour commencer à traiter les réponses.

  5. Après avoir passé en revue quelques cas d'utilisation (bonne demande, mauvaise demande), vous devriez pouvoir obtenir une bonne réponse et des réponses d'erreur typiques de l'API. Enregistrez le bon et les messages d'erreur. Celles-ci sont utiles pour les tests unitaires afin de vous assurer que vous gérez correctement certains types de réponses.

64
S.Lott

C'est plus une réponse supplémentaire à la one déjà donnée :

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

Vous devez rendre la demande injectable afin de pouvoir la remplacer par une maquette lors des 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.

8
hakre

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

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

Vous pouvez soumettre une requête comme vous le feriez normalement à l'API, puis enregistrer cette réponse sous forme de fichier JSON. Vous pouvez ensuite l'utiliser comme une maquette.

Jetez un coup d'œil aux tests de cette bibliothèque qui se connectent à une API à l'aide de Guzzle.

Cela se moque des réponses de l'API, il y a beaucoup d'informations dans la documentation sur le fonctionnement des tests, cela pourrait vous donner une idée de la façon de procéder.

mais fondamentalement, vous appelez manuellement l’API avec tous les paramètres dont vous avez besoin, puis 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 le chargez dans la maquette plutôt que d'utiliser l'api en direct, vous pouvez ensuite tester les données dans la maquette que vous avez créée contient les valeurs attendues.

Ma version mise à jour de l'API en question peut être trouvée ici . Mise à jour du rapport

1
Dizzy Bryan High

L'un des moyens de tester les API externes consiste, comme vous l'avez indiqué, en créant une simulation et en utilisant le comportement codé en dur tel que vous l'avez compris.

Parfois, certaines personnes qualifient ce type de test de test "basé sur le contrat", dans lequel vous pouvez écrire des tests sur l'API en fonction du comportement observé et codé, et lorsque ces tests commencent à échouer, le "contrat est rompu". S'il s'agit de tests simples REST utilisant des données factices, vous pouvez également les exécuter afin que le fournisseur externe puisse s'exécuter afin de lui permettre de savoir où et quand il est sur le point de modifier suffisamment l'API pour qu'il s'agisse d'une nouvelle version ou d'un avertissement de ne pas être rétro-compatible.

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

0
dragon788

S'appuyant sur ce que dit la réponse la plus votée ... Voici comment je l'ai fait et qui fonctionne bien.

  1. Créé un objet simulé curl 
  2. Dites à la maquette quels paramètres elle attendrait
  3. Imaginez quelle sera la réponse de l'appel curl au sein de votre fonction
  4. Laissez votre code faire son truc

    $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);
    
0
Reza S