Quel est le but des objects fictifs?

Je suis novice dans les tests unitaires et j’entends continuellement les mots «objects simulés». En termes simples, est-ce que quelqu’un peut expliquer ce que sont les objects fictifs et à quoi ils servent généralement lors de l’écriture des tests unitaires?

Comme vous dites que vous êtes nouveau dans les tests unitaires et que vous avez demandé des objects simulés en termes simples, je vais essayer l’exemple d’un profane.

Test d’unité

Imaginez des tests unitaires pour ce système:

cook < - waiter <- customer 

Il est généralement facile d'envisager de tester un composant de bas niveau comme le cook :

 cook < - test driver 

Le pilote de test commande simplement différents plats et vérifie que le cuisinier retourne le bon plat pour chaque commande.

Il est plus difficile de tester un composant intermédiaire, comme le serveur, qui utilise le comportement d’autres composants. Un testeur naïf pourrait tester le composant serveur de la même manière que nous avons testé le composant de cuisson:

 cook < - waiter <- test driver 

Le pilote d'essai commandera différents plats et s'assurera que le serveur retourne le bon plat. Malheureusement, cela signifie que ce test du composant serveur peut dépendre du comportement correct du composant cook. Cette dépendance est encore pire si le composant de cuisson a des caractéristiques peu propices aux tests, comme un comportement non déterministe (le menu inclut la surprise du chef comme plat), beaucoup de dépendances (le cuisinier ne cuisinera pas sans son personnel) ou beaucoup de ressources (certains plats nécessitent des ingrédients coûteux ou prennent une heure pour cuisiner).

Comme il s’agit d’un test serveur, idéalement, nous voulons tester uniquement le serveur, pas le cuisinier. Plus précisément, nous voulons nous assurer que le serveur transmet correctement la commande du client au cuisinier et livre correctement la nourriture du cuisinier au client.

Les tests unitaires consistent à tester les unités de manière indépendante. Une meilleure approche consisterait donc à isoler le composant testé (le serveur) en utilisant ce que Fowler appelle des doubles tests (mannequins, stubs, contrefaçons, simulacres) .

  ----------------------- | | v | test cook < - waiter <- test driver 

Ici, le cuisinier de test est "en phase" avec le pilote de test. Idéalement, le système testé est conçu pour que le cuisinier de test puisse être facilement substitué ( injecté ) pour travailler avec le serveur sans modifier le code de production (par exemple sans changer le code du serveur).

Objets factices

Maintenant, le test cook (double test) pourrait être mis en œuvre de différentes manières:

  • un faux cuisinier - une personne prétendant être un cuisinier en utilisant des dîners congelés et un four à micro-ondes,
  • un stub cook - un vendeur de hot-dogs qui vous donne toujours des hot-dogs, peu importe ce que vous commandez, ou
  • un faux cuisinier - un policier infiltré qui suit un scénario en prétendant être un cuisinier dans une opération d'infiltration.

Voir l'article de Fowler pour plus de détails sur les faux vs les stubs contre les simulacres contre les mannequins , mais pour l'instant, concentrons-nous sur un faux cuisinier.

  ----------------------- | | v | mock cook < - waiter <- test driver 

Une grande partie des tests unitaires du composant serveur se concentre sur la manière dont le serveur interagit avec le composant cook. Une approche basée sur la simulation se concentre sur la spécification complète de l'interaction correcte et la détection du moment où elle tourne mal.

L'object simulé sait d'avance ce qui doit se produire pendant le test (par exemple, quels appels de méthodes seront appelés, etc.) et l'object simulé sait comment il est censé réagir (par exemple, quelle valeur de retour fournir). Le simulacre indiquera si ce qui se passe réellement diffère de ce qui est censé se produire. Un object de simulation personnalisé peut être créé à partir de zéro pour chaque scénario de test afin d'exécuter le comportement attendu pour ce scénario de test, mais un environnement de simulation s'efforce de permettre à une spécification de comportement d'être indiquée directement dans le scénario de test.

La conversation entourant un test basé sur des simulations pourrait ressembler à ceci:

test pilote à simuler cuisinier : attendez-vous à une commande de hot-dog et donnez-lui ce hot dog factice en réponse

pilote d'essai (se faisant passer pour un client) au serveur : je voudrais un hot dog s'il vous plait
serveur de se moquer de cuisinier : 1 hot dog s'il vous plaît
simulacre de cuisinier au serveur : commander: 1 hot-dog prêt (donne un hot dog factice au serveur)
serveur pour tester le pilote : voici votre hot-dog (donne un hot dog factice au pilote d'essai)

pilote d'essai : TEST SUCCEEDED!

Mais comme notre serveur est nouveau, voici ce qui pourrait arriver:

test pilote à simuler cuisinier : attendez-vous à une commande de hot-dog et donnez-lui ce hot dog factice en réponse

pilote d'essai (se faisant passer pour un client) au serveur : je voudrais un hot dog s'il vous plait
serveur pour se moquer de cuisinier : 1 hamburger s'il vous plait
cuisinier simulé arrête le test: on m'a dit de m'attendre à une commande de hot-dog!

pilote d'essai note le problème: TEST FAILED! - le serveur a changé la commande

ou

test pilote à simuler cuisinier : attendez-vous à une commande de hot-dog et donnez-lui ce hot dog factice en réponse

pilote d'essai (se faisant passer pour un client) au serveur : je voudrais un hot dog s'il vous plait
serveur de se moquer de cuisinier : 1 hot dog s'il vous plaît
simulacre de cuisinier au serveur : commander: 1 hot-dog prêt (donne un hot dog factice au serveur)
serveur pour tester le pilote : voici vos frites (donne des frites d'un autre ordre pour tester le pilote)

pilote d'essai note les frites inattendues: ÉCHEC DU TEST! le serveur a rendu le mauvais plat

Il peut être difficile de voir clairement la différence entre des objects simulés et des stubs sans un exemple de stub basé sur des contrastes, mais cette réponse est déjà beaucoup trop longue 🙂

Notez également qu'il s'agit d'un exemple assez simpliste et que les frameworks moqueurs autorisent des spécifications assez sophistiquées du comportement attendu des composants pour prendre en charge des tests complets. Il y a beaucoup de matière sur les objects fictifs et les frameworks de simulation pour plus d'informations.

Un object simulé est un object qui remplace un object réel. Dans la programmation orientée object, les objects simulés sont des objects simulés qui imitent le comportement d’objects réels de manière contrôlée.

Un programmeur informatique crée généralement un object simulé pour tester le comportement d’un autre object, de la même manière qu’un concepteur automobile utilise un mannequin pour simuler le comportement dynamic d’un humain lors d’un impact sur un véhicule.

http://en.wikipedia.org/wiki/Mock_object

Les objects fictifs vous permettent de configurer des scénarios de test sans avoir recours à des ressources lourdes et volumineuses, telles que des bases de données. Au lieu d’appeler une firebase database pour le tester, vous pouvez simuler votre firebase database en utilisant un object simulé dans vos tests unitaires. Cela vous évite de devoir installer et supprimer une firebase database réelle, juste pour tester une méthode unique dans votre classe.

Le mot “Mock” est parfois utilisé à tort de manière interchangeable avec “Stub”. Les différences entre les deux mots sont décrites ici. Essentiellement, un simulacre est un object de stub qui inclut également les attentes (c.-à-d. Les «assertions») pour le comportement correct de l’object / de la méthode en cours de test.

Par exemple:

 class OrderInteractionTester... public void testOrderSendsMailIfUnfilled() { Order order = new Order(TALISKER, 51); Mock warehouse = mock(Warehouse.class); Mock mailer = mock(MailService.class); order.setMailer((MailService) mailer.proxy()); mailer.expects(once()).method("send"); warehouse.expects(once()).method("hasInventory") .withAnyArguments() .will(returnValue(false)); order.fill((Warehouse) warehouse.proxy()); } } 

Notez que les objects de simulation d’ warehouse et de mailer sont programmés avec les résultats attendus.

Les objects simulés sont des objects simulés qui imitent le comportement des objects réels. En général, vous écrivez un object simulé si:

  • L’object réel est trop complexe pour être incorporé dans un test unitaire (par exemple, une communication réseau, vous pouvez avoir un object simulé simulant l’autre homologue)
  • Le résultat de votre object est non déterministe
  • L’object réel n’est pas encore disponible

Un object simulé est une sorte de double test . Vous utilisez mockobjects pour tester et vérifier le protocole / l’interaction de la classe testée avec d’autres classes.

En règle générale, vous pouvez définir des attentes de méthode: programme ou enregistrement que vous attendez de votre classe à un object sous-jacent.

Disons par exemple que nous testons une méthode de service pour mettre à jour un champ dans un widget. Et que dans votre architecture, il y a un WidgetDAO qui traite de la firebase database. Parler avec la firebase database est lent et sa configuration et son nettoyage sont compliqués. Nous allons donc essayer le WidgetDao.

pensons à ce que le service doit faire: il doit obtenir un widget à partir de la firebase database, en faire quelque chose et le sauvegarder à nouveau.

Donc, en pseudo-langage avec une pseudo-bibliothèque, nous aurions quelque chose comme:

 Widget sampleWidget = new Widget(); WidgetDao mock = createMock(WidgetDao.class); WidgetService svc = new WidgetService(mock); // record expected calls on the dao expect(mock.getById(id)).andReturn(sampleWidget); expect(mock.save(sampleWidget); // turn the dao in replay mode replay(mock); svc.updateWidgetPrice(id,newPrice); verify(mock); // verify the expected calls were made assertEquals(newPrice,sampleWidget.getPrice()); 

De cette manière, nous pouvons facilement tester le développement de classes qui dépendent d’autres classes.

Je recommande fortement un excellent article de Martin Fowler expliquant ce que sont exactement les moqueries et comment elles diffèrent des souches.

Lorsque vous testez une partie d’un programme informatique, vous devez idéalement tester uniquement le comportement de cette partie.

Par exemple, regardez le pseudo-code ci-dessous à partir d’un élément imaginaire d’un programme qui utilise un autre programme pour appeler quelque chose d’impression:

 If theUserIsFred then Call Printer(HelloFred) Else Call Printer(YouAreNotFred) End 

Si vous testiez ceci, vous voudriez principalement tester la partie qui regarde si l’utilisateur est Fred ou non. Vous ne voulez pas vraiment tester la partie Printer . Ce serait un autre test.

C’est là que les objects Mock entrent en jeu. Ils prétendent être d’autres types de choses. Dans ce cas, vous utiliseriez une Printer simulée pour qu’elle agisse comme une imprimante réelle, mais ne ferait pas de choses gênantes comme l’impression.


Il existe plusieurs autres types d’objects factices que vous ne pouvez pas utiliser. La principale chose qui fait Mocks Mocks est qu’ils peuvent être configurés avec des comportements et des attentes.

Les attentes permettent à votre Mock de générer une erreur lorsqu’il est utilisé de manière incorrecte. Donc, dans l’exemple ci-dessus, vous pouvez être sûr que l’imprimante est appelée avec HelloFred dans le scénario de test “user is Fred”. Si cela ne se produit pas, votre Mock peut vous avertir.

Behavior in Mocks signifie que, par exemple, votre code a fait quelque chose comme:

 If Call Printer(HelloFred) Returned SaidHello Then Do Something End 

Maintenant, vous voulez tester ce que fait votre code lorsque l’imprimante est appelée et renvoie SaidHello, vous pouvez donc configurer le Mock pour qu’il renvoie SaidHello lorsqu’il est appelé avec HelloFred.

Une bonne ressource à ce sujet est Martin Fowlers post Mocks Ar’t Stubs

Les objects simulés et stub constituent une partie essentielle des tests unitaires. En fait, ils consortingbuent grandement à vérifier que vous testez des unités plutôt que des groupes d’unités.

En un mot, vous utilisez des stubs pour casser la dépendance de SUT (System Under Test) sur d’autres objects et des simulacres pour le faire et vérifiez que SUT appelle certaines méthodes / propriétés sur la dépendance. Cela revient aux principes fondamentaux des tests unitaires: les tests doivent être facilement lisibles, rapides et ne nécessitant pas de configuration, ce qui peut impliquer l’utilisation de toutes les classes réelles.

En règle générale, vous pouvez avoir plusieurs bouts dans votre test, mais vous ne devriez en avoir qu’un seul. C’est parce que le but de Mock est de vérifier le comportement et que votre test ne doit tester qu’une seule chose.

Scénario simple utilisant C # et Moq:

 public interface IInput { object Read(); } public interface IOutput { void Write(object data); } class SUT { IInput input; IOutput output; public SUT (IInput input, IOutput output) { this.input = input; this.output = output; } void ReadAndWrite() { var data = input.Read(); output.Write(data); } } [TestMethod] public void ReadAndWriteShouldWriteSameObjectAsRead() { //we want to verify that SUT writes to the output interface //input is a stub, since we don't record any expectations Mock input = new Mock(); //output is a mock, because we want to verify some behavior on it. Mock output = new Mock(); var data = new object(); input.Setup(i=>i.Read()).Returns(data); var sut = new SUT(input.Object, output.Object); //calling verify on a mock object makes the object a mock, with respect to method being verified. output.Verify(o=>o.Write(data)); } 

Dans l’exemple ci-dessus, j’ai utilisé Moq pour montrer des talons et des simulacres. Moq utilise la même classe pour les deux – Mock ce qui le rend un peu déroutant. Indépendamment de l’exécution, le test échouera si output.Write n’est pas appelé avec les données en tant que parameter , alors que l’échec de l’appel input.Read() ne le fera pas échouer.

Comme une autre réponse suggérée via un lien vers ” Mocks Ar’t Stubs “, les simulacres sont une forme de “test double” à utiliser à la place d’un object réel. Ce qui les différencie des autres formes de tests doubles, tels que les objects stub, est que les autres tests doublent la vérification de l’état (et éventuellement la simulation) alors que les simulations offrent la vérification du comportement (et éventuellement la simulation).

Avec un talon, vous pouvez appeler plusieurs méthodes sur le talon dans n’importe quel ordre (ou même de manière répétée) et déterminer le succès si le talon a capturé une valeur ou un état souhaité. En revanche, un object fictif s’attend à ce que des fonctions très spécifiques soient appelées, dans un ordre spécifique, et même un nombre spécifique de fois. Le test avec un object fictif sera considéré comme “échoué” simplement parce que les méthodes ont été appelées dans une séquence ou un compte différent – même si l’object simulé avait l’état correct à la fin du test!

De cette manière, les objects de simulation sont souvent considérés comme plus étroitement couplés au code SUT que les objects de stub. Cela peut être une bonne ou une mauvaise chose, selon ce que vous essayez de vérifier.

L’intérêt d’utiliser des objects fictifs est en partie dû au fait qu’ils ne doivent pas vraiment être implémentés conformément aux spécifications. Ils peuvent simplement donner des réponses factices. Par exemple, si vous devez implémenter les composants A et B, et que les deux “appellent” (interagissez avec), alors vous ne pouvez pas tester A tant que B n’est pas implémenté, et vice versa. Dans le développement piloté par les tests, c’est un problème. Donc, vous créez des objects simulés (“factices”) pour A et B, qui sont très simples, mais ils donnent une sorte de réponse lorsqu’ils sont en interaction. De cette façon, vous pouvez implémenter et tester A en utilisant un object simulé pour B.

Pour php et phpunit est bien expliqué dans la documentation phpunit. voir ici la documentation phpunit

Dans un mot simple, l’object moqueur est juste un object fictif de votre original et renvoie sa valeur de retour, cette valeur de retour peut être utilisée dans la classe de test