wake-up-neo.net

Wie werden Integrationstests für die Interaktion mit externen APIs geschrieben?

Erstens, wo mein Wissen ist bei:

Unit Tests sind solche, die einen kleinen Teil des Codes testen (meist einzelne Methoden).

Integrationstests sind diejenigen, die die Interaktion zwischen mehreren Codebereichen testen (die hoffentlich bereits ihre eigenen Unit-Tests haben). Manchmal erfordern Teile des zu testenden Codes, dass ein anderer Code auf eine bestimmte Art und Weise agiert. Hier kommen Mocks & Stubs ins Spiel. Deshalb verspotten wir einen Teil des Codes, um sehr spezifisch vorzugehen. Dadurch kann unser Integrationstest vorhersehbar ohne Nebenwirkungen ausgeführt werden.

Alle Tests sollten eigenständig ohne Datenaustausch ausgeführt werden können. Wenn die gemeinsame Nutzung von Daten erforderlich ist, ist dies ein Zeichen dafür, dass das System nicht ausreichend entkoppelt ist.

Als nächstes die Situation, der ich gegenüberstehe:

Bei der Interaktion mit einer externen API (insbesondere einer RESTful-API, die Live-Daten mit einer POST -Anforderung ändert) können (sollten?) Wir die Interaktion mit dieser API nachahmen (ausführlicher in diese Antwort ) für einen Integrationstest. Ich verstehe auch, dass wir die einzelnen Komponenten der Interaktion mit dieser API Unit-Test können (Erstellen der Anforderung, Analysieren des Ergebnisses, Auslösen von Fehlern usw.). Was ich nicht verstehe, ist, wie man das tatsächlich macht.

Also zum Schluss: Meine Frage (n).

Wie teste ich meine Interaktion mit einer externen API, die Nebenwirkungen hat?

Ein perfektes Beispiel ist Googles Inhalts-API zum Einkaufen . Um die vorliegende Aufgabe ausführen zu können, ist ein angemessener Vorbereitungsaufwand erforderlich. Anschließend wird die tatsächliche Anforderung ausgeführt und anschließend der Rückgabewert analysiert. Einiges davon ist ohne 'Sandbox'-Umgebung .

Der Code dafür hat im Allgemeinen einige Abstraktionsebenen, etwa:

<?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());
    }
}
?>

Hinweis: Dies ist ein PHP5/PHPUnit-Beispiel

Da testTheRequest die von der Testsuite aufgerufene Methode ist, führt das Beispiel eine Live-Anforderung aus.

Nun wird diese Live-Anfrage (hoffentlich, sofern alles gut gelaufen ist) eine POST-Anfrage ausführen, die den Nebeneffekt hat, Live-Daten zu ändern.

Ist das akzeptabel? Welche Alternativen habe ich? Ich kann keine Möglichkeit finden, das Request-Objekt für den Test zu verspotten. Und selbst wenn ich es tun würde, würde es bedeuten, Ergebnisse/Einstiegspunkte für jeden möglichen Codepfad einzurichten, den die Google-API akzeptiert (der in diesem Fall durch Ausprobieren gefunden werden müsste), aber mir die Verwendung von Fixtures erlauben.

Eine weitere Erweiterung ist, wenn bestimmte Anforderungen darauf beruhen, dass bestimmte Daten bereits live sind. Wenn Sie das Google Content-API erneut als Beispiel verwenden, um einem Unterkonto einen Daten-Feed hinzuzufügen, muss das Unterkonto bereits vorhanden sein.

Ein Ansatz, den ich mir vorstellen kann, sind die folgenden Schritte;

  1. In testCreateAccount
    1. Erstellen Sie ein Unterkonto
    2. Angenommen, das Unterkonto wurde erstellt
    3. Löschen Sie das Unterkonto
  2. Haben testCreateDataFeed abhängig von testCreateAccount keine Fehler
    1. Erstellen Sie in testCreateDataFeed ein neues Konto
    2. Erstellen Sie den Datenfeed
    3. Angenommen, der Daten-Feed wurde erstellt
    4. Löschen Sie den Datenfeed
    5. Löschen Sie das Unterkonto

Dies wirft dann die weitere Frage auf; Wie teste ich das Löschen von Konten/Datenfeeds? testCreateDataFeed fühlt sich für mich schmutzig an - Was passiert, wenn das Erstellen des Datenfeeds fehlschlägt? Der Test schlägt fehl, daher wird das Unterkonto nie gelöscht ... Ich kann das Löschen nicht ohne Erstellung testen. Schreiben Sie daher einen weiteren Test (testDeleteAccount), der sich auf testCreateAccount stützt, bevor Sie ein eigenes Konto erstellen und dann löschen (da Daten nicht gelöscht werden sollten) nicht zwischen den Tests geteilt werden).

In Summe

  • Wie teste ich die Interaktion mit einer externen API, die sich auf Live-Daten auswirkt?
  • Wie kann ich Objekte in einem Integrationstest verspotten, wenn sie sich hinter Abstraktionsebenen verstecken?
  • Was mache ich, wenn ein Test fehlschlägt und die Live-Daten in einem inkonsistenten Zustand verbleiben?
  • Wie im Code mache ich das eigentlich alles?

Verbunden:

66
Jess Telford

Wie teste ich die Interaktion mit einer externen API, die Live-Daten beeinflusst?

Du nicht Sie müssen tatsächlich darauf vertrauen, dass die eigentliche API tatsächlich funktioniert.

Sie können - und sollten - die API mit Live-Daten einsetzen, um sicherzustellen, dass Sie sie verstehen. 

Sie müssen es aber nicht testen. Wenn die API nicht funktioniert, beenden Sie sie einfach. Testen Sie nicht jeden Rand- und Eckfall.

Wie kann ich Objekte in einem Integrationstest nachahmen, wenn sie hinter Abstraktionsebenen verborgen sind?

Das ist der Punkt. Testen Sie die Abstraktion. Sie müssen darauf vertrauen, dass die Implementierung funktioniert. Sie testen Ihren Code. Nicht ihr Code.

Was mache ich, wenn ein Test fehlschlägt und die Live-Daten in einem inkonsistenten Zustand verbleiben?

Was? Warum testen Sie Live-APIs, um sicherzustellen, dass sie funktionieren? Vertraust du ihnen nicht? Wenn Sie ihnen nicht vertrauen, testen Sie nicht. Finden Sie einen Anbieter, dem Sie vertrauen können.

Sie testen nur Ihren Code. Sie vertrauen ihrem Code. Sie verspottet genug von ihrem Code, um sicherzustellen, dass Ihr Code funktioniert.


Wie machst du das.

  1. Mit der API herumspielen. Anfragen senden. Antworten bekommen.

  2. Spielen Sie mit Ihrer App herum. Finden Sie heraus, welche Art von Anfragen Sie senden möchten.

  3. Gehen Sie zurück zur API. Senden Sie eine bekannte gute Anfrage. Holen Sie sich die Antwort. Speichern Sie diese Antwort . Dies ist Ihre Goldstandard-Antwort auf eine gute Anfrage. Canonize dies in einen Testfall. 

  4. Jetzt können Sie an Ihrer App arbeiten und wissen, dass Sie eine Antwort auf Goldstandard haben, die wirklich von der echten API stammt. Das sollte ausreichen, um mit den Antworten fertig zu werden.

  5. Nachdem Sie einige Anwendungsfälle durchgearbeitet haben (gute Anforderung, schlechte Anforderung), sollten Sie in der Lage sein, eine gute Antwort und einige typische Fehlerantworten der API zu erhalten. Speichern Sie die guten und die Fehlermeldungen. Dies ist nützlich für Komponententests, um sicherzustellen, dass Sie einige der Antworten richtig behandeln.

64
S.Lott

Dies ist eher eine zusätzliche Antwort auf das bereits gegebene : :

Beim Durchsehen des Codes hat class GoogleAPIRequest eine hart codierte Abhängigkeit von class Request. Dies verhindert, dass Sie es unabhängig von der Anforderungsklasse testen, sodass Sie die Anforderung nicht nachahmen können.

Sie müssen die Anforderung injektierbar machen, damit Sie sie während des Tests in ein Mock umwandeln können. Dadurch werden keine echten API-HTTP-Anforderungen gesendet, die Live-Daten werden nicht geändert und Sie können viel schneller testen.

8
hakre

Ich musste kürzlich eine Bibliothek aktualisieren, da die API, mit der sie verbunden wird, aktualisiert wurde.

Mein Wissen reicht nicht aus, um es im Detail zu erklären, aber ich habe durch das Betrachten des Codes sehr viel gelernt. https://github.com/gridiron-guru/FantasyDataAPI

Sie können eine Anfrage wie üblich an das API senden und diese Antwort dann als Json-Datei speichern. Sie können diese dann als Mock verwenden.

Sehen Sie sich die Tests in dieser Bibliothek an, die mithilfe von Guzzle mit einem API verbunden ist.

Es spottet die Antworten der API, es gibt viele Informationen in den Dokumenten, wie das Testen funktioniert, und es könnte Ihnen eine Vorstellung davon geben, wie Sie damit umgehen sollen.

grundsätzlich führen Sie jedoch einen manuellen Aufruf des API mit allen erforderlichen Parametern aus und speichern die Antwort als Json-Datei.

Wenn Sie Ihren Test für den API-Aufruf schreiben, die gleichen Parameter mitsenden und laden, damit er im Mock geladen wird, anstatt das Live-API zu verwenden, können Sie dann testen, ob die Daten in dem von Ihnen erstellten Mock die erwarteten Werte enthalten.

Meine aktualisierte Version der betreffenden API finden Sie hier . Aktualisiertes Repo

1

Eine der Möglichkeiten, externe APIs zu testen, besteht darin, wie Sie erwähnt haben, indem Sie einen Mock erstellen und dagegen mit dem Verhalten arbeiten, das Sie so verstanden haben, dass es hart codiert ist.

In manchen Fällen wird diese Art von Tests als "vertragsbasierte" Tests bezeichnet, bei denen Sie Tests anhand der von Ihnen beobachteten und für sie codierten Verhalten gegen die API schreiben können. Wenn diese Tests fehlschlagen, ist der "Vertrag gebrochen". Wenn es sich um einfache REST -basierte Tests mit Dummy-Daten handelt, können Sie sie auch dem externen Anbieter zur Verfügung stellen, sodass sie feststellen können, wo und wann sie die API so stark ändern, dass es sich um eine neue Version handelt oder eine Warnung ausgegeben wird darüber, nicht abwärtskompatibel zu sein.

Ref: https://www.thoughtworks.com/radar/techniques/consumer-driven-contract-testing

0
dragon788

Aufbauend auf dem, was die hoch gewählte Antwort sagt ... So habe ich es gemacht und funktioniert gut.

  1. Erstellt ein Mock-Curl-Objekt 
  2. Sagen Sie dem Schein, welche Parameter er erwarten würde
  3. Verspotten Sie die Reaktion des Curl-Aufrufs in Ihrer Funktion
  4. Lassen Sie Ihren Code das Richtige tun

    $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