Zunächst einmal, wo mein Wissen ist:
Unit-Tests sind solche, die einen kleinen Teil des Codes testen (meistens einzelne Methoden).
Integrationstests sind solche, die die Interaktion zwischen mehreren Codebereichen testen (die hoffentlich bereits eigene Unit-Tests haben). Manchmal erfordern Teile des zu testenden Codes, dass anderer Code auf eine bestimmte Weise funktioniert. Hier kommen Mocks & Stubs ins Spiel. Also verspotten wir einen Teil des Codes, um eine sehr spezifische Leistung zu erzielen. Dadurch kann unser Integrationstest vorhersehbar und ohne Nebenwirkungen ausgeführt werden.
Alle Tests sollten ohne Datenaustausch eigenständig ausgeführt werden können. Wenn Datenaustausch erforderlich ist, ist dies ein Zeichen dafür, dass das System nicht ausreichend entkoppelt ist.
Als nächstes die Situation, mit der ich konfrontiert bin:
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 (in dieser Antwort beredter angegeben ) für einen Integrationstest verspotten . Ich verstehe auch, dass wir die einzelnen Komponenten der Interaktion mit dieser API testen 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 endlich: Meine Frage (n).
Wie teste ich meine Interaktion mit einer externen API, die Nebenwirkungen hat?
Ein perfektes Beispiel ist die Content-API von Google zum Einkaufen . Um die vorliegende Aufgabe ausführen zu können, ist ein angemessener Vorbereitungsaufwand erforderlich. Anschließend wird die eigentliche Anforderung ausgeführt und anschließend der Rückgabewert analysiert. Ein Teil 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 dies testTheRequest
die von der Testsuite aufgerufene Methode ist, führt das Beispiel eine Live-Anforderung aus.
Jetzt wird diese Live-Anfrage (hoffentlich vorausgesetzt, dass alles gut gegangen 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 dies tun würde, würde dies bedeuten, Ergebnisse / Einstiegspunkte für jeden möglichen Codepfad einzurichten, den die Google-API akzeptiert (was in diesem Fall durch Ausprobieren gefunden werden müsste), aber mir die Verwendung von Fixtures erlauben würde.
Eine weitere Erweiterung ist, wenn bestimmte Anforderungen davon abhängen, dass bestimmte Daten bereits aktiv sind. Wenn Sie erneut die Google Content-API als Beispiel verwenden, um einem Unterkonto einen Datenfeed hinzuzufügen, muss das Unterkonto bereits vorhanden sein.
Ein Ansatz, den ich mir vorstellen kann, sind die folgenden Schritte:
- Im
testCreateAccount
- Erstellen Sie ein Unterkonto
- Stellen Sie sicher, dass das Unterkonto erstellt wurde
- Unterkonto löschen
- Habe darauf
testCreateDataFeed
angewiesentestCreateAccount
, keine Fehler zu haben- In
testCreateDataFeed
ein neues Konto erstellen - Erstellen Sie den Datenfeed
- Stellen Sie sicher, dass der Datenfeed erstellt wurde
- Löschen Sie den Datenfeed
- Unterkonto löschen
- In
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 ist, 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. Daher schreibe ich einen weiteren Test ( testDeleteAccount
), auf den testCreateAccount
vor dem Erstellen zurückgegriffen wird, und lösche dann ein eigenes Konto (da Daten dies nicht sollten zwischen 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 / stubben, wenn sie hinter Abstraktionsebenen versteckt sind?
- Was mache ich, wenn ein Test fehlschlägt und die Live-Daten in einem inkonsistenten Zustand verbleiben?
- Wie mache ich das alles im Code ?
Verbunden: