J'ai testé ma classe à l'unité, comment puis-je commencer avec un test d'intégration?

19

J'ai écrit une classe qui gère les destinataires sur une liste MailChimp, appelée MailChimpRecipient. Il utilise la classe MCAPI, qui est un wrapper d'API tiers.

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

Je passe l'objet MCAPI dans le constructeur de l'objet MailChimpRecipient, j'ai donc écrit des tests unitaires en utilisant PHPUnit qui testent toute la logique de ma propre classe (je ne teste pas la classe MCAPI). J'ai une couverture de code à 100% et tous les tests réussissent. Cela se fait en se moquant et en stubant l'objet MCAPI.

Ma prochaine étape était d'écrire un test d'intégration, également en utilisant PHPUnit, où je construirais le luminaire MailChimpRecipient en utilisant un véritable objet MCAPI, configuré pour utiliser une vraie liste MailChimp.

J'ai écrit ce que je pense être un test d'intégration, qui exécute essentiellement des tests contre l'interface publique de l'objet, comme:

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

Le test «d'intégration» ne teste aucun des éléments internes de la classe - il s'assure simplement que, étant donné un véritable objet MCAPI, il se comporte comme annoncé.

Est-ce correct? Est-ce la meilleure façon d'exécuter un test d'intergation? Après tout, les internes ont été testés avec un test unitaire. Ai-je raison de penser que le test d'intégration est là pour vérifier qu'il fonctionne vraiment, selon la façon dont son comportement est annoncé?

Pour aller plus loin, la classe MailChimpRecipient implémente une interface, qui sera également implémentée par d'autres classes. L'idée est d'utiliser une fabrique pour transmettre différents types d'objets destinataires de liste de diffusion à mon code, qui font tous la même chose, mais en utilisant différents fournisseurs de liste de diffusion. Étant donné que mes tests d'intégration testent cette interface, que diriez-vous de l'utiliser pour toutes les classes qui implémentent l'interface? Ensuite, à l'avenir, si je conçois une nouvelle classe à utiliser de manière interchangeable, je peux exécuter le même test d'intégration avant de l'insérer dans un projet.

Cela vous semble-t-il raisonnable? Les tests unitaires testent les composants internes d'un objet, les tests d'intégration s'assurent qu'il se comporte comme annoncé?

Lewis Bassett
la source
4
Je pense que vous avez trop de logique dans votre test. Vous exécutez beaucoup de code jusqu'à ce que vous fassiez l'assertion. Vous souhaitez probablement tester la suppression d'un destinataire en premier. Mais cela ne répond pas à votre question, juste un commentaire.
hakre
1
Eh bien, vous devriez utiliser la setUpfonction pour établir les motifs pour exécuter vos tests. Si l'entrée n'est pas définie, vous ne pouvez pas vraiment tester. L'entrée doit être précise, stricte et toujours la même. Si une condition préalable d'un test n'est pas remplie, ignorez le test à la place. Ensuite, analysez pourquoi il saute et si vous devez ajouter des tests supplémentaires et / ou si cela setUpn'est pas fait correctement.
hakre
1
Ne codez pas non plus les valeurs de test en dur dans un test qui lui est propre, mais faites en sorte que ces membres de la classe puissent être partagés entre les tests (et modifiés à un endroit central) ou utilisés DataProvider(c'est une fonction offrant une entrée en tant que paramètres à un test).
hakre
1
Saisie dans le sens de tout ce sur quoi fonctionne votre fonction de test. Lorsque vous testez l'ajout d'un destinataire et que vous souhaitez vous assurer qu'il n'existe pas déjà, vous devez au moins affirmer la suppression au cas où il interviendrait. Dans le cas contraire, la condition préalable de votre test n'est pas garantie d'être testable.
hakre
1
+1 pour une bonne question, mais a également voté pour migrer vers les programmeurs. Semble que c'est là que les questions sur les stratégies de test appartiennent
GordonM

Réponses:

17

Lors du test de votre code, vous devez faire attention à trois domaines:

  • Test de scénario
  • Test fonctionel
  • Tests unitaires

Normalement, la quantité de test que vous avez dans chaque catégorie aurait la forme d'une pyramide, ce qui signifie beaucoup de tests unitaires en bas, quelques tests fonctionnels au milieu et seulement quelques tests de scénario.

Avec un test unitaire, vous simulez tout ce que la classe testée utilise et vous le testez dans l'isolement pur (c'est pourquoi il est important de s'assurer qu'à l'intérieur de votre classe vous récupérez toutes les dépendances par injection afin qu'elles puissent être remplacées sous test).

Avec les tests unitaires, vous testez toutes les possibilités, donc non seulement le «chemin heureux» mais aussi toutes les conditions d'erreur.

Si vous êtes absolument certain que toutes vos unités fonctionnent isolément, vous écrivez quelques tests (tests fonctionnels) pour vous assurer que les unités fonctionnent également lorsqu'elles sont combinées. Ensuite, vous écrivez un test de scénario, qui teste le câblage entre tous les modules fonctionnels.

Par exemple, supposons que vous testiez une voiture.

Vous pouvez assembler toute la voiture et en tant que conducteur vérifier toutes les conditions possibles, mais ce serait vraiment difficile à faire.

Au lieu de cela, vous testeriez une petite partie du moteur avec toutes les possibilités (test unitaire)

Ensuite, vous testez l'ensemble du moteur (séparé de la voiture) qui serait un test fonctionnel.

Comme dernier test, vous introduisez votre clé, démarrez la voiture et la conduisez jusqu'au parking. Si cela fonctionne, vous savez que toutes les pièces (batterie, carburant, moteur, ..) sont connectées et puisque vous les avez testées isolément, vous pouvez être sûr que toute la voiture fonctionne correctement.

Donc, dans votre cas, vous avez testé toutes les conditions d'erreur et le cheminement heureux dans votre test unitaire et savez que vous n'avez qu'à effectuer un test de bout en bout avec les `` vrais composants '' pour vérifier si le câblage est correct.

Quelques autres points,

  • Évitez la logique conditionnelle dans votre test unitaire. Si vous devez nettoyer, vous utilisez une sorte d'état global et les tests peuvent soudainement s'influencer mutuellement.
  • Ne spécifiez aucune donnée non pertinente pour le test. Si je modifiais le prénom ou le nom de famille, le test échouerait-il? Probablement pas parce que c'est l'adresse e-mail qui est importante mais parce que vous la mentionnez explicitement dans votre test, je ne peux pas en être sûr. Essayez de regarder le modèle de générateur pour créer vos données de test et le rendre explicite ce qui est vraiment important.
Wouter de Kort
la source
Merci, cela confirme beaucoup de ce que je pensais. Juste pour clarifier - ce n'est PAS un test unitaire. J'ai déjà écrit un test unitaire, qui teste l'objet en isolation complète et a une couverture de code à 100% de l'objet. C'était censé être un test d'intégration, pour m'assurer qu'il fonctionne lorsque j'y injecte un véritable objet MCAPI. J'ai juste besoin de supprimer tous les destinataires qui sont ajoutés à la liste - c'est tout le nettoyage, et il est mis en œuvre pour garantir qu'aucun des tests ne s'influencent mutuellement. Que suggéreriez-vous à la place?
1
Ouais! J'ai cru comprendre que vous aviez déjà fait les tests unitaires. L'objet MCAPI garde-t-il une trace des destinataires et est-ce le nettoyage que vous devez faire? Si c'est le problème des tiers, vous ne pouvez rien faire dans un test d'intégration. Si vous, de l'autre, gardez une trace de la liste, vous devez vous assurer que vous évitez les données globales (et les singletons) pour vous assurer que les tests ne s'influencent pas mutuellement. Dans un monde parfait, nettoyer les choses lorsqu'un test commence / se termine, indique un défaut de conception, mais dans le monde réel, vous ne pouvez pas toujours l'éviter.
Wouter de Kort
1
J'ajouterais que les tests de scénarios ne sont probablement pas vraiment adaptés à PHPUnit. Yu voudra peut-être regarder un outil que vous pouvez exécuter dans un navigateur tel que Selenium, ou un outil qui peut simuler un navigateur, comme jMeter.
GordonM
Merci les gars! Il y a certainement beaucoup à apprendre quand il s'agit d'écrire un bon code testable, n'est-ce pas? Je me suis commandé un exemplaire de ce livre: amazon.co.uk/… . J'espère que ce que vous avez tous dit aura un peu plus de sens après avoir lu cela. @Wouter, je supprime simplement un destinataire, car le test aurait entraîné l'ajout d'une adresse e-mail à la liste. Je le supprime pour que la liste ne soit pas affectée par ce test.
1
@LewisBassett Je ne suis pas un développeur Php mais les modèles de test xUnit ( amazon.com/xUnit-Test-Patterns-Refactoring-Code/dp/0131495054 ) sont définitivement une bonne lecture. Les articles sur misko.hevery.com/code-reviewers-guide sont également très intéressants.
Wouter de Kort