web-dev-qa-db-fra.com

Quel est le but des objets fictifs?

Je suis nouveau dans le domaine des tests unitaires et j'entends continuellement les mots "objets fictifs" qui jaillissent beaucoup. En termes simples, quelqu'un peut-il expliquer ce que sont des objets fictifs et à quoi ils servent généralement lors de l'écriture de tests unitaires?

155
agentbanks217

Puisque vous dites que vous êtes nouveau dans les tests unitaires et que vous avez demandé des objets fictifs en termes "profanes", je vais essayer l'exemple d'un profane.

Tests unitaires

Imaginez des tests unitaires pour ce système:

cook <- waiter <- customer

Il est généralement facile d'imaginer tester un composant de bas niveau tel que cook:

cook <- test driver

Le testeur commande simplement différents plats et vérifie que le cuisinier retourne le plat correct pour chaque commande.

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

cook <- waiter <- test driver

Le pilote d’essai commanderait différents plats et s’assurerait que le serveur lui retourne le plat correct. Malheureusement, cela signifie que ce test du composant serveur peut dépendre du comportement correct du composant de cuisson. Cette dépendance est encore pire si le composant de cuisson présente des caractéristiques contraires au test, comme un comportement non déterministe (le menu inclut la surprise du chef), de nombreuses dépendances (le cuisinier ne cuisinera pas sans tout son personnel) ou beaucoup ressources (certains plats nécessitent des ingrédients coûteux ou prennent une heure à cuire).

Puisqu'il s'agit d'un test de serveur, idéalement, nous souhaitons 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 les aliments du cuisinier au client.

Le test unitaire signifie le test des unités indépendamment, une meilleure approche serait donc d'isoler le composant testé (le serveur) en utilisant quoi les appels de Fowler doublent les tests (mannequins, talons, faux, simulacres) .

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

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

Faux Objets

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

  • un faux cuisinier - quelqu'un prétendant être cuisinier en utilisant des dîners glacés et un micro-ondes,
  • un petit cuisinier - un vendeur de hot-dogs qui vous donne toujours des hot-dogs, peu importe ce que vous commandez, ou
  • un simulacre de cuisine - un policier sous couverture suivant un scénario prétendant être cuisinier dans une opération d'infiltration.

Voir article de Fowler pour plus de détails sur les contrefaçons vs talons vs simulacres vs mannequins , mais pour l'instant, concentrons-nous sur un simulacre de cuisine.

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

Une grande partie de l'unité qui teste le composant serveur se concentre sur la façon dont le serveur interagit avec le composant de cuisson. Une approche fictive se concentre sur la spécification complète de l'interaction correcte et sur la détection des dysfonctionnements.

L'objet fictif sait à l'avance ce qui est supposé se produire pendant le test (par exemple, quelles sont les appels de méthodes qui seront invoqués, etc.) et l'objet simulé sait comment il est censé réagir (par exemple, quelle valeur de retour à fournir). La maquette indiquera si ce qui se passe réellement diffère de ce qui est censé se passer. Un objet fictif personnalisé peut être créé à partir de zéro pour chaque cas de test afin d'exécuter le comportement attendu pour ce cas de test, mais une structure moqueuse s'efforce de permettre à une telle spécification de comportement d'être clairement et facilement indiquée directement dans le cas de test.

La conversation autour d'un test simulé pourrait ressembler à ceci:

pilote d'essai à simulacre de cuisine : attend un hot-dog ordre et donnez-lui ce hot-dog factice en réponse

test driver (en tant que client) à serveur : Je voudrais un hot-dog s'il vous plaît
serveur à simulateur de cuisine : 1 hot dog s'il vous plaît
maquette de cuisinier à serveur : commande: 1 hot-dog prêt (donne un hot-dog factice au serveur)
serveur à pilote de test : voici votre hot-dog (donne un hot-dog factice au pilote)

test driver : TEST RÉUSSI!

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

pilote d'essai à simulacre de cuisine : attend un hot-dog ordre et donnez-lui ce hot-dog factice en réponse

test driver (en tant que client) à serveur : Je voudrais un hot-dog s'il vous plaît
serveur à simulateur de cuisine : 1 hamburger s'il vous plaît
maquette de cuisinier arrête le test: On m'a dit d'attendre une commande de hot-dogs !

test driver note le problème: TEST FAILED! - le serveur a changé la commande

ou

pilote d'essai à simulacre de cuisine : attend un hot-dog ordre et donnez-lui ce hot-dog factice en réponse

test driver (en tant que client) à serveur : Je voudrais un hot-dog s'il vous plaît
serveur à simulateur de cuisine : 1 hot dog s'il vous plaît
maquette de cuisinier à serveur : commande: 1 hot-dog prêt (donne un hot-dog factice au serveur)
serveur à pilote de test : voici vos frites (donne des frites d'un autre ordre pour tester le pilote)

test driver note les frites inattendues: TEST FAILED! le serveur a rendu le mauvais plat

Il peut être difficile de voir clairement la différence entre les objets fictifs et les talons sans un exemple contrasté basé sur le talon, mais cette réponse est déjà bien 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 afin de prendre en charge des tests complets. Il y a beaucoup de matériel sur les objets fictifs et les frameworks moqueurs pour plus d'informations.

332
Bert F

Un objet factice est un objet qui se substitue à un objet réel. Dans la programmation orientée objet, les objets fantaisie sont des objets simulés qui imitent le comportement d'objets réels de manière contrôlée.

Un programmeur informatique crée généralement un objet fictif pour tester le comportement d'un autre objet, de la même manière qu'un concepteur de voiture utilise un mannequin pour le test de collision afin de simuler le comportement dynamique d'un humain lors d'impacts de véhicules.

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

Les objets fantaisie vous permettent de configurer des scénarios de test sans utiliser de grandes ressources lourdes telles que des bases de données. Au lieu d'appeler une base de données à des fins de test, vous pouvez simuler votre base de données à l'aide d'un objet fictif dans vos tests unitaires. Cela vous évite d'avoir à configurer et à démonter une vraie base de données, juste pour tester une seule méthode 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 faux est un objet de talon qui inclut également les attentes (c'est-à-dire des "assertions") quant au comportement correct de l'objet/de la méthode à tester.

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 objets warehouse et mailer sont programmés avec les résultats attendus.

27
Robert Harvey

Les objets fictifs sont des objets simulés qui imitent le comportement des objets réels. En règle générale, vous écrivez un objet fictif si:

  • L'objet réel est trop complexe pour l'incorporer dans un test unitaire (par exemple, une communication réseau, vous pouvez avoir un objet fictif qui simule l'autre homologue)
  • Le résultat de votre objet est non déterministe
  • L'objet réel n'est pas encore disponible
15
Dani Cricco

Un objet factice est une sorte de Test Double . Vous utilisez des mockobjects pour tester et vérifier le protocole/interaction de la classe à tester avec d'autres classes.

Typiquement, vous aurez en quelque sorte une attente de 'programme' ou 'd'enregistrement': les appels de méthode que vous attendez de votre classe pour un objet sous-jacent.

Disons par exemple que nous testons une méthode de service pour mettre à jour un champ dans un widget. Et dans votre architecture, il existe un WidgetDAO qui traite de la base de données. Parler avec la base de données est lent, son installation et son nettoyage sont compliqués, nous allons donc nous moquer du WidgetDao.

réfléchissons à ce que le service doit faire: il doit extraire un widget de la base de données, en faire quelque chose et le sauvegarder à nouveau.

Donc, en pseudo-langage avec une bibliothèque pseudo-fictive, 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 lecteur de classes dépendant d'autres classes.

12
Peter Tillemans

Je recommande vivement un excellent article de Martin Fowler expliquant ce que sont exactement des sarcasmes et en quoi ils diffèrent des moignons.

11
Adam Byrtek

Lorsque vous testez à l'unité une partie d'un programme informatique, vous souhaitez idéalement tester uniquement le comportement de cette partie particulière.

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

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

Si vous testiez cela, vous voudriez surtout 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 ici que les objets factices entrent en jeu. Ils prétendent être d'autres types de choses. Dans ce cas, vous utiliseriez un Mock Printer pour qu’il se comporte comme une véritable imprimante, sans pour autant faire des choses gênantes comme l’impression.


Il existe plusieurs autres types d’objets que vous pouvez utiliser et qui ne sont pas des Mocks. La principale caractéristique de Mocks Mocks est qu’ils peuvent être configurés avec des comportements et des attentes.

Les attentes permettent à votre maquette de générer une erreur en cas d'utilisation incorrecte. Ainsi, dans l'exemple ci-dessus, vous pouvez vouloir vous assurer que l'imprimante est appelée avec HelloFred dans le cas de test "user is Fred". Si cela ne se produit pas, votre simulacre peut vous prévenir.

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 le comportement de votre code lors de l'appel de l'imprimante et renvoyer SaidHello. Vous pouvez donc configurer le simulacre pour qu'il retourne SaidHello lorsqu'il est appelé avec HelloFred.

Une bonne ressource à ce sujet est le post de Martin Fowlers Mocks Ar not Stubs

9
David Hall

Les objets factices et tronqués constituent une partie cruciale des tests unitaires. En fait, ils font beaucoup pour s'assurer que vous testez nités, plutôt que groupes d'unités.

En un mot, vous utilisez des stubs pour rompre la dépendance du système sous test avec d'autres objets et vous moque de cela et vérifiez que le système a appelé certaines méthodes/propriétés de la dépendance. Cela renvoie aux principes fondamentaux des tests unitaires, à savoir que les tests doivent être facilement lisibles, rapides et ne nécessitant pas de configuration, ce qui pourrait impliquer l'utilisation de toutes les classes réelles.

En règle générale, votre test peut comporter plusieurs moignons, mais vous ne devez en avoir qu'un. En effet, le but de la simulation est de vérifier le comportement et votre test ne doit tester qu'une 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<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  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 illustrer les moignons et les moqueries. Moq utilise la même classe pour les deux - Mock<T>, Ce qui le rend un peu déroutant. Quoi qu'il en soit, au moment de l'exécution, le test échouera si output.Write N'est pas appelé avec des données telles que parameter, alors que l'échec de l'appel de input.Read() n'échouera pas.

7
Igor Zevaka

Comme une autre réponse suggérée via un lien vers " Mocks Is not Stubs ", les mocks sont une forme de "test double" à utiliser à la place d'un objet réel. Ce qui les différencie des autres types de doubles de test, tels que les objets de bout, réside dans le fait que d'autres doubles de test offrent une vérification d'état (et éventuellement une simulation) alors que les simulacres offrent une vérification du comportement (et éventuellement une simulation).

Avec un stub, vous pouvez appeler plusieurs méthodes sur le stub dans n’importe quel ordre (ou même de façon répétée) et déterminer si le stub a capturé la valeur ou l’état que vous aviez prévu. En revanche, un objet fantaisie s'attend à ce que des fonctions très spécifiques soient appelées, dans un ordre spécifique et même un nombre de fois spécifique. Le test avec un objet fictif sera considéré comme "échoué" simplement parce que les méthodes ont été invoquées dans une séquence ou un compte différent, même si l'objet fictif avait l'état correct à la fin du test!

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

4
Brent Arias

Une partie de l'intérêt d'utiliser des objets fictifs est qu'ils ne doivent pas nécessairement ê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 s'appellent (interagissent), vous ne pouvez pas tester A tant que B n'est pas implémenté, et inversement. En développement piloté par les tests, c'est un problème. Donc, vous créez des objets factices ("factices") pour A et B, qui sont très simples, mais ils donnent une sorte de réponse de certains quand ils interagissent. De cette façon, vous pouvez implémenter et tester A en utilisant un objet fictif pour B.

3
LarsH

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

Dans Word simple, l'objet moqueur n'est qu'un objet factice de votre objet d'origine et renvoie sa valeur de retour. Cette valeur de retour peut être utilisée dans la classe de test.

1
Gautam Rai