Quelle est la différence entre truquer, moquer et tronquer?


706

Je sais comment j'utilise ces termes, mais je me demande s'il existe des définitions acceptées pour truquer , se moquer et écraser pour les tests unitaires? Comment les définissez-vous pour vos tests? Décrivez les situations dans lesquelles vous pourriez les utiliser.

Voici comment je les utilise:

Fake : une classe qui implémente une interface mais contient des données fixes et aucune logique. Renvoie simplement les «bonnes» ou «mauvaises» données selon la mise en œuvre.

Mock : une classe qui implémente une interface et permet de définir dynamiquement les valeurs à renvoyer / exceptions à lancer à partir de méthodes particulières et offre la possibilité de vérifier si des méthodes particulières ont été appelées / non appelées.

Stub : comme une classe fictive, sauf qu'elle ne permet pas de vérifier que les méthodes ont été appelées / non appelées.

Les maquettes et les talons peuvent être générés à la main ou générés par un cadre de simulation. Les fausses classes sont générées à la main. J'utilise principalement des simulations pour vérifier les interactions entre ma classe et les classes dépendantes. J'utilise des talons une fois que j'ai vérifié les interactions et que je teste des chemins alternatifs dans mon code. J'utilise de fausses classes principalement pour faire abstraction des dépendances de données ou lorsque les mocks / stubs sont trop fastidieux à configurer à chaque fois.


6
Eh bien, vous avez tout dit dans votre "question" :) Je pense que ce sont des définitions assez bien acceptées de ces termes
Eran Galperin

2
La définition Wikipédia de Fake diffère de cela, affirmant qu'un Fake "est utilisé comme une implémentation plus simple, par exemple en utilisant une base de données en mémoire dans les tests au lieu de faire un véritable accès à la base de données)" Voir en.wikipedia.org/wiki/Test_double
zumalifeguard

2
J'ai beaucoup appris de la ressource suivante, avec une excellente explication de Robert C. Martin (oncle Bob): The Little Mocker sur The Clean Code Blog . Il explique les différences et les subtilités des mannequins, teste les doubles, les talons, les espions, les (vrais) faux et les faux. Il mentionne également Martin Fowler et explique un peu l'historique des tests de logiciels.
Erik

testing.googleblog.com/2013/07/… (un bref résumé d'une page).
ShreevatsaR

Voici mon point de vue pour expliquer cela: Test Doubles: Fakes, Stubs and Mocks (article de blog avec des exemples)
michal-lipski

Réponses:


548

Vous pouvez obtenir des informations:

De Martin Fowler à propos de Mock and Stub

Les faux objets ont en fait des implémentations fonctionnelles, mais prennent généralement un raccourci qui les rend non adaptés à la production

Les talons fournissent des réponses prédéfinies aux appels effectués pendant le test, ne répondant généralement pas du tout à ce qui est en dehors de ce qui est programmé pour le test. Les talons peuvent également enregistrer des informations sur les appels, comme un talon de passerelle de messagerie qui se souvient des messages qu'il a «envoyés», ou peut-être seulement du nombre de messages qu'il a «envoyés».

Les maquette sont ce dont nous parlons ici: des objets préprogrammés avec des attentes qui forment une spécification des appels qu'ils sont censés recevoir.

De xunitpattern :

Faux : Nous acquérons ou construisons une implémentation très légère de la même fonctionnalité que celle fournie par un composant dont dépend le SUT et demandons au SUT de l'utiliser au lieu du réel.

Stub : cette implémentation est configurée pour répondre aux appels du SUT avec les valeurs (ou exceptions) qui exerceront le code non testé (voir Bogues de production à la page X) dans le SUT. Une indication clé pour l'utilisation d'un talon de test est d'avoir un code non testé dû à l'incapacité de contrôler les entrées indirectes du SUT

Mock Object qui implémente la même interface qu'un objet dont dépend le SUT (System Under Test). Nous pouvons utiliser un objet simulé comme point d'observation lorsque nous devons effectuer une vérification du comportement pour éviter d'avoir une exigence non testée (voir Bogues de production à la page X) causée par une incapacité à observer les effets secondaires de l'invocation de méthodes sur le SUT.

Personnellement

J'essaie de simplifier en utilisant: Mock et Stub. J'utilise Mock lorsqu'il s'agit d'un objet qui renvoie une valeur définie sur la classe testée. J'utilise Stub pour imiter une classe Interface ou Abstract à tester. En fait, peu importe comment vous l'appelez, ce sont toutes des classes qui ne sont pas utilisées en production et sont utilisées comme classes utilitaires pour les tests.


9
Il me semble que les définitions de Stub et Fake sont inversées dans la citation xUnitPattern par rapport à la citation de Martin Fowler. De plus, les définitions de Stub and Fake de Martin Fowler sont inversées par rapport aux définitions de la question initiale de tvanfosson. En réalité, existe-t-il des définitions généralement acceptées de ces deux termes ou cela dépend-il seulement de qui vous parlez?
Simon Tewsi

3
+1 pour "J'essaie de simplifier en utilisant: Mock and Stub". C'est une bonne idée!
Brad Cupit

4
Je ne vois pas comment utiliser uniquement Mock et Stub est une excellente idée. Chaque double test a ses objectifs et donc ses utilisations.
Hector Ordonez

1
Je ne vois pas la différence entre Fake et Mock dans la définition de MF.
IdontCareAboutReputationPoints

2
@MusuNaji: Dans la définition de MF, il n'y a pas "d'attentes" en ce qui concerne la conversation pour un faux, à part qu'il a une implémentation pour son interface. D'un autre côté, le Mock sera contesté (cette méthode a-t-elle été appelée?).
dbalakirev

205

Stub - un objet qui fournit des réponses prédéfinies aux appels de méthode.

Mock - un objet sur lequel vous définissez des attentes.

Faux - un objet aux capacités limitées (à des fins de test), par exemple un faux service Web.

Test Double est le terme général pour les talons, les faux et les faux. Mais de manière informelle, vous entendrez souvent des gens les appeler simplement des simulacres.


4
Quelqu'un pourrait-il m'expliquer et définir ce qu'est une «réponse en conserve» dans ce contexte?
MasterMastic

14
Une valeur explicite, plutôt qu'une valeur calculée.
Mike

Finalement! Quelques définitions que je peux comprendre! Sur la base de ces définitions, alors, googletest (gtest) / googlemock (gmock) permet également aux objets simulés d'être des stubs, car vous pouvez créer des EXPECT_CALL()s sur une méthode simulée qui force certaines sorties en fonction de certaines entrées, en utilisant le type .WillOnce(Invoke(my_func_or_lambda_func))(ou avec .WillRepeatedly()) syntaxe attachée à un EXPECT_CALL(). Quelques exemples d'utilisation Invoke()peuvent être vus dans un contexte différent au bas de ma longue réponse ici: stackoverflow.com/a/60905880/4561887 .
Gabriel Staples

La documentation de Gmock sur Invoke()est ici: github.com/google/googletest/blob/master/googlemock/docs/… . Quoi qu'il en soit, la conclusion est la suivante: Google mock (gmock) permet de créer facilement des mocks et des stubs , bien que la plupart des mocks ne soient pas des stubs.
Gabriel Staples

Les Mocks sont un surensemble de Stubs, ils peuvent toujours renvoyer des réponses prédéfinies mais permettent également au développeur de définir des attentes. Certaines bibliothèques de l'OMI brouillent les lignes de tous les mannequins de test.
Luke

94

Je suis surpris que cette question existe depuis si longtemps et personne n'a encore fourni de réponse basée sur "The Art of Unit Testing" de Roy Osherove .

Dans "3.1 Présentation des stubs", un stub est défini comme:

Un stub est un remplacement contrôlable d'une dépendance (ou d'un collaborateur) existante dans le système. En utilisant un stub, vous pouvez tester votre code sans traiter directement la dépendance.

Et définit la différence entre les talons et les faux comme:

La principale chose à retenir à propos des faux contre les moignons est que les faux sont comme des moignons, mais vous affirmez contre l'objet factice, alors que vous n'affirmez pas contre un moignon.

Fake n'est que le nom utilisé pour les talons et les simulacres. Par exemple lorsque vous ne vous souciez pas de la distinction entre les talons et les faux

La façon dont Osherove fait la distinction entre les talons et les faux, signifie que toute classe utilisée comme faux pour les tests peut être à la fois un talon ou un faux. Ce que c'est pour un test spécifique dépend entièrement de la façon dont vous écrivez les chèques dans votre test.

  • Lorsque votre test vérifie les valeurs dans la classe sous test, ou en fait n'importe où sauf le faux, le faux a été utilisé comme talon. Il a juste fourni des valeurs pour la classe testée à utiliser, soit directement via des valeurs retournées par des appels dessus, soit indirectement en provoquant des effets secondaires (dans certains états) à la suite d'appels dessus.
  • Lorsque votre test vérifie les valeurs du faux, il a été utilisé comme maquette.

Exemple de test où la classe FakeX est utilisée comme stub:

const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);

cut.SquareIt;

Assert.AreEqual(25, cut.SomeProperty);

L' fakeinstance est utilisée en tant que stub car le Assertn'utilise pas fakedu tout.

Exemple de test où la classe de test X est utilisée comme maquette:

const pleaseReturn5 = 5;
var fake = new FakeX(pleaseReturn5);
var cut = new ClassUnderTest(fake);

cut.SquareIt;

Assert.AreEqual(25, fake.SomeProperty);

Dans ce cas, la Assertvérifie une valeur fake, ce qui en fait un faux.

Maintenant, bien sûr, ces exemples sont très artificiels, mais je vois un grand mérite dans cette distinction. Il vous fait savoir comment vous testez vos trucs et où se trouvent les dépendances de votre test.

Je suis d'accord avec Osherove que

d'un point de vue purement maintenable, dans mes tests, l'utilisation de maquettes crée plus de problèmes que de ne pas les utiliser. Ça a été mon expérience, mais j'apprends toujours quelque chose de nouveau.

Affirmer contre le faux est quelque chose que vous voulez vraiment éviter car cela rend vos tests très dépendants de l'implémentation d'une classe qui n'est pas du tout celle qui est testée. Ce qui signifie que les tests de classe ActualClassUnderTestpeuvent commencer à se casser car l'implémentation de a ClassUsedAsMockchangé. Et cela m'envoie une odeur nauséabonde. Les tests pour ActualClassUnderTestne devraient de préférence se casser que lors d'un ActualClassUnderTestchangement.

Je me rends compte qu'écrire des affirmations contre le faux est une pratique courante, surtout lorsque vous êtes un abonné TDD de type moqueur. Je suppose que je suis fermement avec Martin Fowler dans le camp classique (voir "Les moqueurs ne sont pas des talons" de Martin Fowler ) et, comme Osherove, évitez autant que possible les tests d'interaction (ce qui ne peut être fait qu'en affirmant contre les faux).

Pour une lecture amusante sur les raisons pour lesquelles vous devriez éviter les moqueries telles que définies ici, google pour "fowler mockist classicist". Vous trouverez une pléthore d'opinions.


30

Comme mentionné dans la réponse la plus votée, Martin Fowler discute de ces distinctions dans les moqueries ne sont pas des talons , et en particulier le sous-titre The Difference Between Mocks and Stubs , alors assurez-vous de lire cet article.

Plutôt que de se concentrer sur la façon dont ces choses sont différentes, je pense qu'il est plus instructif de se concentrer sur la raison pour laquelle il s'agit de concepts distincts. Chacun existe dans un but différent.

Faux

Un faux est une implémentation qui se comporte "naturellement", mais n'est pas "réelle". Ce sont des concepts flous et donc différentes personnes ont des compréhensions différentes de ce qui rend les choses fausses.

Un exemple de faux est une base de données en mémoire (par exemple en utilisant sqlite avec le :memory:magasin). Vous ne l'utiliseriez jamais pour la production (car les données ne sont pas persistantes), mais c'est parfaitement adéquat comme base de données à utiliser dans un environnement de test. Il est également beaucoup plus léger qu'une "vraie" base de données.

Comme autre exemple, vous utilisez peut-être une sorte de magasin d'objets (par exemple Amazon S3) en production, mais dans un test, vous pouvez simplement enregistrer des objets dans des fichiers sur le disque; alors votre implémentation "enregistrer sur disque" serait un faux. (Ou vous pouvez même simuler l'opération "enregistrer sur le disque" en utilisant un système de fichiers en mémoire à la place.)

Comme troisième exemple, imaginez un objet qui fournit une API de cache; un objet qui implémente la bonne interface mais qui n'effectue simplement pas de mise en cache mais renvoie toujours un échec de cache serait une sorte de faux.

Le but d'un faux n'est pas d'affecter le comportement du système testé , mais plutôt de simplifier l'implémentation du test (en supprimant les dépendances inutiles ou lourdes).

Bouts

UNE stub est une implémentation qui se comporte "de façon anormale". Il est préconfiguré (généralement par la configuration de test) pour répondre à des entrées spécifiques avec des sorties spécifiques.

Le but d'un stub est de faire tester votre système dans un état spécifique. Par exemple, si vous écrivez un test pour du code qui interagit avec une API REST, vous pouvez supprimer l'API REST avec une API qui renvoie toujours une réponse prédéfinie ou qui répond à une demande d'API avec une erreur spécifique. De cette façon, vous pourriez écrire des tests qui font des affirmations sur la façon dont le système réagit à ces états; par exemple, tester la réponse de vos utilisateurs si l'API renvoie une erreur 404.

Un stub est généralement implémenté pour ne répondre qu'aux interactions exactes auxquelles vous lui avez demandé de répondre. Mais la caractéristique clé qui fait de quelque chose un talon est son objectif : un stub consiste à configurer votre cas de test.

Se moquer

Une maquette est similaire à un stub, mais avec une vérification ajoutée. Le but d'une maquette est de faire des affirmations sur la façon dont votre système testé interagit avec la dépendance .

Par exemple, si vous écrivez un test pour un système qui télécharge des fichiers sur un site Web, vous pouvez créer une maquette qui accepte un fichier et que vous pouvez utiliser pour affirmer que le fichier téléchargé était correct. Ou, à plus petite échelle, il est courant d'utiliser une maquette d'un objet pour vérifier que le système testé appelle des méthodes spécifiques de l'objet simulé.

Les simulations sont liées aux tests d'interaction , qui est une méthodologie de test spécifique. Les personnes qui préfèrent tester l' état du système plutôt que les interactions système utiliseront les simulations avec modération, voire pas du tout.

Double test

Les contrefaçons, les talons et les simulacres appartiennent tous à la catégorie des doubles de test . Un double de test est tout objet ou système que vous utilisez dans un test au lieu de quelque chose d'autre. La plupart des tests de logiciels automatisés impliquent l'utilisation de tests doubles d'une sorte ou d'une autre. D'autres types de doubles de test comprennent des valeurs fictives , des espions , et E / S blackholes .


11

Pour illustrer l'utilisation des talons et des maquettes, je voudrais également inclure un exemple basé sur « L'art des tests unitaires » de Roy Osherove .

Imaginez, nous avons une application LogAnalyzer qui a la seule fonctionnalité d'imprimer des journaux. Il doit non seulement parler à un service Web, mais si le service Web génère une erreur, LogAnalyzer doit consigner l'erreur dans une autre dépendance externe, en l'envoyant par e-mail à l'administrateur du service Web.

Voici la logique que nous aimerions tester dans LogAnalyzer:

if(fileName.Length<8)
{
 try
  {
    service.LogError("Filename too short:" + fileName);
  }
 catch (Exception e)
  {
    email.SendEmail("a","subject",e.Message);
  }
}

Comment testez-vous que LogAnalyzer appelle correctement le service de messagerie lorsque le service Web lève une exception? Voici les questions auxquelles nous sommes confrontés:

  • Comment remplacer le service Web?

  • Comment pouvons-nous simuler une exception du service Web afin de pouvoir tester l'appel au service de messagerie?

  • Comment saurons-nous que le service de messagerie a été appelé correctement ou pas du tout?

Nous pouvons traiter les deux premières questions en utilisant un talon pour le service Web . Pour résoudre le troisième problème, nous pouvons utiliser un objet factice pour le service de messagerie .

Un faux est un terme générique qui peut être utilisé pour décrire un talon ou une maquette. Dans notre test, nous aurons deux faux. L'une sera la maquette du service de messagerie, que nous utiliserons pour vérifier que les paramètres corrects ont été envoyés au service de messagerie. L'autre sera un talon que nous utiliserons pour simuler une exception levée à partir du service Web. C'est un talon parce que nous n'utiliserons pas le faux service Web pour vérifier le résultat du test, uniquement pour nous assurer que le test fonctionne correctement. Le service de messagerie électronique est une maquette, car nous affirmerons qu'il a été appelé correctement.

[TestFixture]
public class LogAnalyzer2Tests
{
[Test]
 public void Analyze_WebServiceThrows_SendsEmail()
 {
   StubService stubService = new StubService();
   stubService.ToThrow= new Exception("fake exception");
   MockEmailService mockEmail = new MockEmailService();

   LogAnalyzer2 log = new LogAnalyzer2();
   log.Service = stubService
   log.Email=mockEmail;
   string tooShortFileName="abc.ext";
   log.Analyze(tooShortFileName);

   Assert.AreEqual("a",mockEmail.To); //MOCKING USED
   Assert.AreEqual("fake exception",mockEmail.Body); //MOCKING USED
   Assert.AreEqual("subject",mockEmail.Subject);
 }
}

9

la chose que vous affirmez dessus est appelée un objet simulé et tout le reste qui a simplement aidé le test est un talon .


1
tandis que d'autres réponses ont beaucoup de détails et sont vraiment bonnes. celui-ci le rend si clair et facile à faire la différence, il est difficile de ne pas voter. gj!
Mario Garcia

6

Il s'agit de rendre les tests expressifs. Je mets des attentes sur une maquette si je veux que le test décrive une relation entre deux objets. Je stub renvoie des valeurs si je configure un objet de support pour m'amener au comportement intéressant dans le test.


6

Si vous connaissez Arrange-Act-Assert, alors une façon d'expliquer la différence entre stub et mock qui pourrait vous être utile est que les stubs appartiennent à la section arrange, comme ils le sont pour organiser l'état d'entrée, et que les mocks appartiennent à la section assert comme ils le sont pour affirmer les résultats.

Les nuls ne font rien. Ils sont juste pour remplir des listes de paramètres, afin que vous n'obteniez pas d'erreurs indéfinies ou nulles. Ils existent également pour satisfaire le vérificateur de type dans des langages strictement typés, afin que vous puissiez être autorisé à compiler et à exécuter.


3

Stub, Fakes et Mocks ont des significations différentes selon les différentes sources. Je vous suggère de présenter les termes internes de votre équipe et de vous mettre d'accord sur leur signification.

Je pense qu'il est important de distinguer deux approches: - la validation du comportement (implique une substitution de comportement) - la validation de l'état final (implique l'émulation du comportement)

Pensez à envoyer des e-mails en cas d'erreur. Lors de la validation du comportement - vous vérifiez que la méthode Sendde a IEmailSenderété exécutée une fois. Et vous devez émuler le résultat de retour de cette méthode, renvoyer l'ID du message envoyé. Alors vous dites: "Je m'attends à ce queSend soit appelé. Et je vais simplement renvoyer un identifiant factice (ou aléatoire) pour tout appel" . C'est la validation du comportement: emailSender.Expect(es=>es.Send(anyThing)).Return((subject,body) => "dummyId")

Lors de la validation de l'état, vous devrez créer TestEmailSenderces implémentations IEmailSender. Et implémentez la Sendméthode - en enregistrant l'entrée dans une structure de données qui sera utilisée pour la vérification de l'état futur comme un tableau de certains objets SentEmails, puis il teste que vous vérifierez queSentEmails contient l'e-mail attendu. Il s'agit d'une validation d'état: Assert.AreEqual(1, emailSender.SentEmails.Count)

D'après mes lectures, j'ai compris que la validation du comportement s'appelait généralement Mocks . Et la validation Etat habituellement appelé Stubs ou Fakes .


Définition vraiment bien détaillée et nette.
shyam sundar singh tomar

2

stub et fake sont des objets en ce sens qu'ils peuvent varier leur réponse en fonction des paramètres d'entrée. la principale différence entre eux est qu'un faux est plus proche d'une implémentation du monde réel qu'un stub. Les talons contiennent essentiellement des réponses codées en dur à une demande attendue. Voyons un exemple:

public class MyUnitTest {

 @Test
 public void testConcatenate() {
  StubDependency stubDependency = new StubDependency();
  int result = stubDependency.toNumber("one", "two");
  assertEquals("onetwo", result);
 }
}

public class StubDependency() {
 public int toNumber(string param) {
  if (param == “one”) {
   return 1;
  }
  if (param == “two”) {
   return 2;
  }
 }
}

Une maquette est une étape par rapport aux contrefaçons et aux talons. Les maquettes offrent les mêmes fonctionnalités que les talons mais sont plus complexes. Ils peuvent avoir des règles définies pour eux qui dictent dans quel ordre les méthodes de leur API doivent être appelées. La plupart des simulateurs peuvent suivre le nombre de fois qu'une méthode a été appelée et peuvent réagir en fonction de ces informations. Les simulateurs connaissent généralement le contexte de chaque appel et peuvent réagir différemment selon les situations. Pour cette raison, les moqueries nécessitent une certaine connaissance de la classe dont elles se moquent. un stub ne peut généralement pas suivre le nombre de fois qu'une méthode a été appelée ou dans quel ordre une séquence de méthodes a été appelée. Une maquette ressemble à:

public class MockADependency {

 private int ShouldCallTwice;
 private boolean ShouldCallAtEnd;
 private boolean ShouldCallFirst;

 public int StringToInteger(String s) {
  if (s == "abc") {
   return 1;
  }
  if (s == "xyz") {
   return 2;
  }
  return 0;
 }

 public void ShouldCallFirst() {
  if ((ShouldCallTwice > 0) || ShouldCallAtEnd)
   throw new AssertionException("ShouldCallFirst not first thod called");
  ShouldCallFirst = true;
 }

 public int ShouldCallTwice(string s) {
  if (!ShouldCallFirst)
   throw new AssertionException("ShouldCallTwice called before ShouldCallFirst");
  if (ShouldCallAtEnd)
   throw new AssertionException("ShouldCallTwice called after ShouldCallAtEnd");
  if (ShouldCallTwice >= 2)
   throw new AssertionException("ShouldCallTwice called more than twice");
  ShouldCallTwice++;
  return StringToInteger(s);
 }

 public void ShouldCallAtEnd() {
  if (!ShouldCallFirst)
   throw new AssertionException("ShouldCallAtEnd called before ShouldCallFirst");
  if (ShouldCallTwice != 2) throw new AssertionException("ShouldCallTwice not called twice");
  ShouldCallAtEnd = true;
 }

}

1

fake objectest une véritable implémentation d'interface (protocole) ou une extension utilisant l'héritage ou d'autres approches qui peuvent être utilisées pour créer - est une dépendance. Habituellement, il est créé par le développeur comme une solution la plus simple pour remplacer une certaine dépendance

stub objectest un objet nu (0, nil et méthodes sans logique) avec et supplémentaire et prédéfinie (par développeur) état pour définir des valeurs retournées. Habituellement, il est créé par le cadre

mock objectest très similaire à stub objectmais l' état supplémentaire est changé pendant l'exécution du programme pour vérifier si quelque chose s'est produit (la méthode a été appelée).

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.