Ich habe meine Klasse getestet. Wie fange ich mit einem Integrationstest an?

19

Ich habe eine Klasse namens MailChimpRecipient geschrieben, die Empfänger in einer MailChimp-Liste verwaltet. Es wird die MCAPI-Klasse verwendet, bei der es sich um einen API-Wrapper eines Drittanbieters handelt.

http://apidocs.mailchimp.com/api/1.3/ http://apidocs.mailchimp.com/api/downloads/

Ich übergebe das MCAPI-Objekt an den Konstruktor des MailChimpRecipient-Objekts, daher habe ich Unit-Tests mit PHPUnit geschrieben, die die gesamte Logik in meiner eigenen Klasse testen (ich teste nicht die MCAPI-Klasse). Ich habe 100% Codeabdeckung und alle Tests bestehen. Dies erfolgt durch Verspotten und Stoppen des MCAPI-Objekts.

Mein nächster Schritt war das Schreiben eines Integrationstests, ebenfalls mit PHPUnit, bei dem ich das MailChimpRecipient-Fixture unter Verwendung eines echten MCAPI-Objekts konstruierte, das für die Verwendung einer echten MailChimp-Liste eingerichtet war.

Ich habe geschrieben, was ich für einen Integrationstest halte, der im Grunde genommen Tests gegen die öffentliche Schnittstelle des Objekts ausführt, wie:

public function testAddedRecipientCanBeFound()
{
    $emailAddress = '[email protected]';
    $forename = 'Fred';
    $surname = 'Smith';

    // First, delete the email address if it is already on the list
    $oldRecipient = $this->createRecipient();
    if($oldRecipient->find($emailAddress))
    {
        $oldRecipient->delete();
    }
    unset($oldRecipient);

    // Add the recipient using the test data
    $newRecipient = $this->createRecipient();
    $newRecipient->setForename($forename);
    $newRecipient->setSurname($surname);
    $newRecipient->setEmailAddress($emailAddress);
    $newRecipient->add();
    unset($newRecipient);

    // Assert that the recipient can be found using the same email address
    $this->assertTrue($this->_recipient->find($emailAddress));
}

Der "Integration" -Test testet keine der Interna der Klasse - er stellt nur sicher, dass sich ein reales MCAPI-Objekt so verhält, wie es angekündigt wurde.

Ist das richtig? Ist dies der beste Weg, um einen Intergationstest durchzuführen? Immerhin wurden die Einbauten mit einem Unit-Test geprüft. Habe ich Recht, wenn ich denke, dass der Integrationstest dazu da ist, zu testen, ob er wirklich funktioniert, je nachdem, wie für sein Verhalten geworben wird?

Um noch einen Schritt weiter zu gehen, implementiert die MailChimpRecipient-Klasse eine Schnittstelle, die auch von anderen Klassen implementiert wird. Die Idee ist, eine Factory zu verwenden, um verschiedene Arten von Empfängerobjekten für Mailinglisten an meinen Code zu übergeben, die alle dasselbe tun, obwohl sie verschiedene Mailinglistenanbieter verwenden. Da meine Integrationstests diese Schnittstelle testen, können Sie sie dann für alle Klassen verwenden, die die Schnittstelle implementieren. Wenn ich dann in Zukunft eine neue Klasse entwerfe, die austauschbar verwendet werden soll, kann ich denselben Integrationstest ausführen, bevor ich sie in ein Projekt einfüge.

Hört sich das vernünftig an? Die Unit-Tests testen die Interna eines Objekts. Integrations-Tests stellen sicher, dass es sich wie angekündigt verhält.

Lewis Bassett
quelle
4
Ich denke, Sie haben zu viel Logik in Ihrem Test. Sie führen viel Code aus, bis Sie die Behauptung aufstellen. Sie möchten wahrscheinlich zuerst die Löschung eines Empfängers testen. Aber das ist keine Antwort auf Ihre Frage, nur ein Kommentar.
Hakre
1
Nun, Sie sollten die setUpFunktion nutzen, um die Gründe für die Durchführung Ihrer Tests zu ermitteln. Wenn die Eingabe undefiniert ist, können Sie sie nicht wirklich testen. Die Eingabe muss präzise, ​​streng und immer gleich sein. Wenn eine Testvoraussetzung nicht erfüllt ist, überspringen Sie stattdessen den Test. Analysieren Sie dann, warum es überspringt und ob Sie zusätzliche Tests hinzufügen müssen und / oder ob das setUpnicht richtig gemacht wurde.
Hakre
1
Codieren Sie Testwerte wahrscheinlich auch nicht in einem eigenen Test fest, sondern definieren Sie diese Klassenmitglieder so, dass sie von Test zu Test geteilt (und an einer zentralen Stelle geändert) oder verwendet werden können DataProvider(dies ist eine Funktion, die Eingaben als Parameter für einen Test bietet).
Hakre
1
Eingabe im Sinne von allem, woran Ihre Testfunktion arbeitet. Wenn Sie das Hinzufügen eines Empfängers testen und sicherstellen möchten, dass dieser noch nicht vorhanden ist, sollten Sie zumindest die Löschung geltend machen, falls er aktiviert wird. Andernfalls kann die Testvoraussetzung nicht als überprüfbar eingestuft werden.
Hakre
1
+1 für gute Frage, stimmte aber auch für die Migration zu Programmierern. Scheint, dass Fragen zu
Teststrategien

Antworten:

17

Beim Testen Ihres Codes sollten Sie auf drei Bereiche achten:

  • Szenariotests
  • Funktionsprüfung
  • Unit-Test

Normalerweise hat die Testmenge, die Sie in jeder Kategorie haben, die Form einer Pyramide, was bedeutet, dass unten viele Einheitentests, in der Mitte einige Funktionstests und nur einige Szenarientests ausgeführt werden.

Mit einem Komponententest verspotten Sie alles, was die getestete Klasse verwendet, und testen es in reiner Isolation (aus diesem Grund ist es wichtig, sicherzustellen, dass Sie in Ihrer Klasse alle Abhängigkeiten durch Injection abrufen, damit sie im Test ersetzt werden können).

Mit Unit-Tests testen Sie alle Möglichkeiten, also nicht nur den "Happy Path", sondern auch alle Fehlerzustände.

Wenn Sie völlig sicher sind, dass alle Einheiten isoliert arbeiten, schreiben Sie einige Tests (Funktionstests), um sicherzustellen, dass die Einheiten auch in Kombination funktionieren. Dann schreiben Sie einen Szenariotest, der die Verdrahtung zwischen allen Funktionsmodulen testet.

Angenommen, Sie testen ein Auto.

Sie könnten das ganze Auto zusammenbauen und als Fahrer jeden möglichen Zustand überprüfen, aber das wäre wirklich schwer zu tun.

Stattdessen würden Sie einen kleinen Teil des Motors mit allen Möglichkeiten testen (Unit Test)

Dann testen Sie den gesamten Motor (getrennt vom Auto), was eine Funktionsprüfung wäre.

Als letzten Test geben Sie Ihren Schlüssel ein, starten das Auto und fahren es zum Parkplatz. Wenn das funktioniert, wissen Sie, dass alle Teile (Batterie, Kraftstoff, Motor usw.) verbunden sind, und da Sie sie isoliert getestet haben, können Sie sich ziemlich sicher sein, dass das gesamte Auto ordnungsgemäß funktioniert.

In Ihrem Fall haben Sie also alle Fehlerbedingungen und den fehlerfreien Pfad in Ihrem Komponententest getestet und wissen, dass Sie nur einen End-to-End-Test mit den „echten Komponenten“ durchführen müssen, um zu überprüfen, ob die Verkabelung korrekt ist.

Ein paar andere Punkte,

  • Vermeiden Sie bedingte Logik in Ihrem Komponententest. Wenn Sie aufräumen müssen, können sich die Verwendung eines globalen Status und die Tests plötzlich gegenseitig beeinflussen.
  • Geben Sie keine Daten an, die für den Test nicht relevant sind. Wenn ich den Vor- oder Nachnamen ändern würde, würde der Test fehlschlagen? Wahrscheinlich nicht, weil es die E-Mail-Adresse ist, die wichtig ist, aber weil Sie sie in Ihrem Test explizit erwähnen, kann ich nicht sicher sein. Schauen Sie sich das Builder-Muster an, um Ihre Testdaten zu erstellen und zu verdeutlichen, was wirklich wichtig ist.
Wouter de Kort
quelle
Danke, das bestätigt eine Menge meiner Gedanken. Nur zur Verdeutlichung - dies ist KEIN Komponententest. Ich habe bereits einen Komponententest geschrieben, der das Objekt vollständig isoliert testet und das Objekt zu 100% mit Code abdeckt. Dies sollte ein Integrationstest sein, um sicherzustellen, dass er funktioniert, wenn ich ein echtes MCAPI-Objekt einspeise. Ich muss nur alle Empfänger löschen, die der Liste hinzugefügt wurden - das ist alles, was aufgeräumt wird, und es wurde implementiert, um sicherzustellen, dass sich keiner der Tests gegenseitig beeinflusst. Was würden Sie stattdessen vorschlagen?
1
Ja! Ich habe verstanden, dass Sie bereits die Unit-Tests durchgeführt haben. Verfolgt das MCAPI-Objekt die Empfänger und ist dies die Bereinigung, die Sie durchführen müssen? Wenn es sich um das Problem eines Drittanbieters handelt, können Sie in einem Integrationstest nichts dagegen unternehmen. Wenn Sie andererseits die Liste verfolgen, sollten Sie sicherstellen, dass Sie globale Daten (und Singletons) vermeiden, um sicherzustellen, dass sich die Tests nicht gegenseitig beeinflussen. In einer perfekten Welt deutet das Aufräumen, wenn ein Test beginnt / endet, auf einen Konstruktionsfehler hin, aber in der realen Welt kann man es nicht immer vermeiden.
Wouter de Kort
1
Ich würde hinzufügen, dass das Testen von Szenarien wahrscheinlich nicht wirklich für PHPUnit geeignet ist. Vielleicht möchten Sie sich ein Tool ansehen, das Sie in einem Browser wie Selenium ausführen können, oder ein Tool, das einen Browser wie jMeter simulieren kann.
GordonM
Danke Leute! Es gibt sicher eine Menge zu lernen, wenn es darum geht, guten testbaren Code zu schreiben, nicht wahr? Ich habe mir ein Exemplar dieses Buches bestellt: amazon.co.uk/… . Hoffentlich macht das, was Sie alle gesagt haben, ein bisschen mehr Sinn, nachdem ich das gelesen habe. @Wouter, ich lösche nur einen Empfänger, weil der Test dazu geführt hätte, dass eine E-Mail-Adresse zur Liste hinzugefügt wurde. Ich lösche es, damit die Liste von diesem Test nicht betroffen ist.
1
@ LewisBassett Ich bin kein PHP-Entwickler, aber die xUnit-Testmuster ( amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054 ) sind definitiv eine gute Lektüre. Auch die Artikel unter misko.hevery.com/code-reviewers-guide sind wirklich interessant.
Wouter de Kort