J'ai lu divers articles sur la moquerie contre le stubbing dans les tests, y compris les moqueries ne sont pas des talons de Martin Fowler , mais je ne comprends toujours pas la différence.
J'ai lu divers articles sur la moquerie contre le stubbing dans les tests, y compris les moqueries ne sont pas des talons de Martin Fowler , mais je ne comprends toujours pas la différence.
Réponses:
Talon
Je crois que la plus grande distinction est qu'un talon que vous avez déjà écrit avec un comportement prédéterminé. Ainsi, vous auriez une classe qui implémente la dépendance (classe abstraite ou interface la plus probable) que vous simulez à des fins de test et les méthodes seraient simplement tronquées avec des réponses définies. Ils ne feraient rien d'extraordinaire et vous auriez déjà écrit le code tronqué pour cela en dehors de votre test.
Moquer
Une maquette est quelque chose qui, dans le cadre de votre test, doit être configuré selon vos attentes. Une maquette n'est pas configurée d'une manière prédéterminée, vous avez donc du code qui le fait dans votre test. Les mocks d'une certaine manière sont déterminés lors de l'exécution, car le code qui définit les attentes doit s'exécuter avant de faire quoi que ce soit.
Différence entre les simulacres et les talons
Les tests écrits avec des initialize -> set expectations -> exercise -> verify
simulations suivent généralement un modèle de test. Alors que le talon pré-écrit suivrait un initialize -> exercise -> verify
.
Similitude entre les simulacres et les talons
Le but des deux est d'éliminer le test de toutes les dépendances d'une classe ou d'une fonction afin que vos tests soient plus ciblés et plus simples dans ce qu'ils essaient de prouver.
Il existe plusieurs définitions d'objets, qui ne sont pas réelles. Le terme général est test double . Ce terme englobe: mannequin , faux , talon , faux .
Selon l'article de Martin Fowler :
- Les objets factices sont transmis mais jamais réellement utilisés. Habituellement, ils sont juste utilisés pour remplir des listes de paramètres.
- Les faux objets ont en fait des implémentations fonctionnelles, mais prennent généralement un raccourci qui les rend impropres à la production (une base de données en mémoire est un bon exemple).
- 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.
Mocks vs Stubs = Test comportemental vs test d'état
Selon le principe du test, une seule chose par test , il peut y avoir plusieurs talons dans un test, mais généralement il n'y a qu'une seule maquette.
Testez le cycle de vie avec des talons:
Testez le cycle de vie avec des simulacres:
Les tests de mocks et de stubs donnent une réponse à la question: quel est le résultat?
Les tests avec des simulations sont également intéressés par: Comment le résultat a-t-il été obtenu?
Un talon est un simple faux objet. Il s'assure simplement que le test se déroule correctement.
Une maquette est un talon plus intelligent. Vous vérifiez que votre test le traverse.
Voici une description de chacun suivi d'un échantillon du monde réel.
Factice - juste de fausses valeurs pour satisfaire le API
.
Exemple : Si vous testez une méthode d'une classe qui nécessite de nombreux paramètres obligatoires dans un constructeur qui n'ont aucun effet sur votre test, vous pouvez créer des objets factices dans le but de créer de nouvelles instances d'une classe.
Fake - crée une implémentation de test d'une classe qui peut dépendre d'une infrastructure externe. (Il est recommandé que votre test unitaire n'interagisse PAS réellement avec l'infrastructure externe.)
Exemple : créez une fausse implémentation pour accéder à une base de données, remplacez-la par une
in-memory
collection.
Stub - substitue des méthodes pour renvoyer des valeurs codées en dur, également appelées state-based
.
Exemple : votre classe de test dépend d'une méthode qui
Calculate()
prend 5 minutes. Plutôt que d'attendre 5 minutes, vous pouvez remplacer sa véritable implémentation par un stub qui renvoie des valeurs codées en dur; ne prenant qu'une petite fraction du temps.
Mock - très similaire Stub
mais interaction-based
basé sur un état. Cela signifie que vous ne vous attendez pas Mock
à renvoyer une valeur, mais à supposer que l'ordre spécifique des appels de méthode est effectué.
Exemple: vous testez une classe d'enregistrement d'utilisateurs. Après avoir appelé
Save
, il devrait appelerSendConfirmationEmail
.
Stubs
et Mocks
sont en fait des sous-types de Mock
, à la fois échanger une implémentation réelle avec une implémentation de test, mais pour des raisons différentes et spécifiques.
Dans le cours codeschool.com , Rails Testing for Zombies , ils donnent cette définition des termes:
Talon
Pour remplacer une méthode par du code qui renvoie un résultat spécifié.
Moquer
Un stub avec une affirmation que la méthode est appelée.
Ainsi, comme Sean Copenhaver l'a décrit dans sa réponse, la différence est que les simulacres établissent des attentes (c'est-à-dire qu'ils font des affirmations, à savoir si ou comment ils sont appelés).
Les talons n'échouent pas à vos tests, la maquette peut.
Je pense que la réponse la plus simple et la plus claire à cette question est donnée par Roy Osherove dans son livre The art of Unit Testing (page 85)
La façon la plus simple de savoir que nous avons affaire à un talon est de remarquer que le talon ne peut jamais échouer au test. Les assertions que le test utilise sont toujours contre la classe testée.
D'un autre côté, le test utilisera un objet factice pour vérifier si le test a échoué ou non. [...]
Encore une fois, l'objet factice est l'objet que nous utilisons pour voir si le test a échoué ou non.
Cela signifie que si vous faites des affirmations contre le faux, cela signifie que vous utilisez le faux comme une maquette, si vous utilisez le faux uniquement pour exécuter le test sans affirmation, vous utilisez le faux comme talon.
En lisant toutes les explications ci-dessus, permettez-moi d'essayer de condenser:
Un Mock teste simplement le comportement, en s'assurant que certaines méthodes sont appelées. Un Stub est une version testable (en soi) d'un objet particulier.
Que voulez-vous dire par Apple?
Si vous le comparez au débogage:
Stub, c'est comme s'assurer qu'une méthode retourne la valeur correcte
Mock, c'est comme entrer dans la méthode et s'assurer que tout ce qui se trouve à l'intérieur est correct avant de retourner la valeur correcte.
L'utilisation d'un modèle mental m'a vraiment aidé à comprendre cela, plutôt que toutes les explications et articles, qui ne «s'enfonçaient» pas tout à fait.
Imaginez que votre enfant a une plaque de verre sur la table et qu'il commence à jouer avec. Maintenant, vous avez peur que ça se casse. Alors, donnez-lui plutôt une assiette en plastique. Ce serait un Mock (même comportement, même interface, implémentation "plus douce").
Maintenant, disons que vous n'avez pas le remplacement en plastique, alors vous expliquez "Si vous continuez à jouer avec, il se cassera!". C'est un bout , vous avez fourni un état prédéfini à l'avance.
Un mannequin serait la fourchette qu'il n'utilisait même pas ... et un espion pourrait être quelque chose comme fournir la même explication que vous avez déjà utilisée qui fonctionnait.
Je pense que la différence la plus importante entre eux est leurs intentions.
Permettez-moi d'essayer de l'expliquer dans POURQUOI talon vs POURQUOI maquette
Supposons que j'écris du code de test pour le contrôleur de chronologie publique de mon client mac twitter
Voici un exemple de code de test
twitter_api.stub(:public_timeline).and_return(public_timeline_array)
client_ui.should_receive(:insert_timeline_above).with(public_timeline_array)
controller.refresh_public_timeline
En écrivant mock, vous découvrez la relation de collaboration des objets en vérifiant que les attentes sont satisfaites, tandis que stub ne simule que le comportement de l'objet.
Je suggère de lire cet article si vous essayez d'en savoir plus sur les simulacres: http://jmock.org/oopsla2004.pdf
Pour être très clair et pratique:
Stub: une classe ou un objet qui implémente les méthodes de la classe / de l'objet à falsifier et renvoie toujours ce que vous voulez.
Exemple en JavaScript:
var Stub = {
method_a: function(param_a, param_b){
return 'This is an static result';
}
}
Mock: Identique à stub, mais il ajoute une logique qui "vérifie" lorsqu'une méthode est appelée afin que vous puissiez être sûr qu'une implémentation appelle cette méthode.
Comme le dit @mLevan, imaginez comme exemple que vous testez une classe d'enregistrement d'utilisateurs. Après avoir appelé Save, il doit appeler SendConfirmationEmail.
Un code très stupide Exemple:
var Mock = {
calls: {
method_a: 0
}
method_a: function(param_a, param_b){
this.method_a++;
console.log('Mock.method_a its been called!');
}
}
Cette diapositive explique très bien les principales différences.
* Extrait de la conférence 16 du CSE 403, Université de Washington (diapositive créée par "Marty Stepp")
J'aime l'explication donnée par Roy Osherove [lien vidéo] .
Chaque classe ou objet créé est un faux. Il s'agit d'une simulation si vous vérifiez les appels par rapport à elle. Sinon, c'est un talon.
voyons Test Doubles:
Stub : Stub est un objet qui contient des données prédéfinies et les utilise pour répondre aux appels pendant les tests. Tels que : un objet qui doit récupérer des données de la base de données pour répondre à un appel de méthode.
Mocks : les Mocks sont des objets qui enregistrent les appels qu'ils reçoivent. Dans l'assertion de test, nous pouvons vérifier sur Mocks que toutes les actions attendues ont été effectuées. Tels que : une fonctionnalité qui appelle le service d'envoi d'e-mails. pour en savoir plus, vérifiez ceci .
Un faux est un terme générique qui peut être utilisé pour décrire soit un stub soit un faux objet (manuscrit ou autre), car ils ressemblent tous les deux au vrai objet.
Le fait qu'un faux soit un talon ou une maquette dépend de la façon dont il est utilisé dans le test actuel. S'il est utilisé pour vérifier une interaction (contre laquelle il est affirmé), il s'agit d'un objet factice. Sinon, c'est un talon.
Fakes garantit le bon déroulement du test. Cela signifie que le lecteur de votre futur test comprendra quel sera le comportement du faux objet, sans avoir besoin de lire son code source (sans avoir besoin de dépendre de ressources externes).
Que signifie le bon déroulement du test?
Par exemple dans le code ci-dessous:
public void Analyze(string filename)
{
if(filename.Length<8)
{
try
{
errorService.LogError("long file entered named:" + filename);
}
catch (Exception e)
{
mailService.SendEMail("admin@hotmail.com", "ErrorOnWebService", "someerror");
}
}
}
Vous souhaitez tester la méthode mailService.SendEMail () , pour ce faire, vous devez simuler une exception dans votre méthode de test, il vous suffit donc de créer une classe Fake Stub errorService pour simuler ce résultat, puis votre code de test pourra tester méthode mailService.SendEMail (). Comme vous le voyez, vous devez simuler un résultat provenant d'une autre classe External Dependency ErrorService.
Dès le papier Mock Roles, pas Objects , par les développeurs de jMock:
Les stubs sont des implémentations factices du code de production qui renvoient des résultats prédéfinis. Les objets fantômes agissent comme des talons, mais incluent également des assertions pour instrumenter les interactions de l'objet cible avec ses voisins.
Ainsi, les principales différences sont:
Pour résumer, tout en essayant de dissiper la confusion du titre de l' article de Fowler : les simulacres sont des talons, mais ce ne sont pas seulement des talons .
Je lisais L'art des tests unitaires et suis tombé sur la définition suivante:
Un faux est un terme générique qui peut être utilisé pour décrire soit un stub soit un faux objet (manuscrit ou autre), car ils ressemblent tous les deux au vrai objet. Le fait qu'un faux soit un talon ou une maquette dépend de la façon dont il est utilisé dans le test actuel. s'il est utilisé pour vérifier une interaction (contre laquelle il est affirmé), c'est un objet factice . Sinon, c'est un talon .
Je suis tombé sur cet article intéressant d'UncleBob The Little Mocker . Il explique toute la terminologie d'une manière très facile à comprendre, donc c'est utile pour les débutants. L'article de Martin Fowlers est difficile à lire, surtout pour les débutants comme moi.
Talon nous aide à exécuter le test. Comment? Il donne des valeurs qui aident à exécuter le test. Ces valeurs ne sont pas elles-mêmes réelles et nous avons créé ces valeurs juste pour exécuter le test. Par exemple, nous créons un HashMap pour nous donner des valeurs similaires aux valeurs de la table de base de données. Ainsi, au lieu d'interagir directement avec la base de données, nous interagissons avec Hashmap.
Mock est un faux objet qui exécute le test. où nous mettons assert.
Voir ci-dessous un exemple de simulations vs stubs utilisant le framework C # et Moq. Moq n'a pas de mot clé spécial pour Stub mais vous pouvez également utiliser l'objet Mock pour créer des stubs.
namespace UnitTestProject2
{
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
[TestClass]
public class UnitTest1
{
/// <summary>
/// Test using Mock to Verify that GetNameWithPrefix method calls Repository GetName method "once" when Id is greater than Zero
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsTwelve_GetNameCalledOnce()
{
// Arrange
var mockEntityRepository = new Mock<IEntityRepository>();
mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
var entity = new EntityClass(mockEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(12);
// Assert
mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Once);
}
/// <summary>
/// Test using Mock to Verify that GetNameWithPrefix method doesn't call Repository GetName method when Id is Zero
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsZero_GetNameNeverCalled()
{
// Arrange
var mockEntityRepository = new Mock<IEntityRepository>();
mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
var entity = new EntityClass(mockEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(0);
// Assert
mockEntityRepository.Verify(m => m.GetName(It.IsAny<int>()), Times.Never);
}
/// <summary>
/// Test using Stub to Verify that GetNameWithPrefix method returns Name with a Prefix
/// </summary>
[TestMethod]
public void GetNameWithPrefix_IdIsTwelve_ReturnsNameWithPrefix()
{
// Arrange
var stubEntityRepository = new Mock<IEntityRepository>();
stubEntityRepository.Setup(m => m.GetName(It.IsAny<int>()))
.Returns("Stub");
const string EXPECTED_NAME_WITH_PREFIX = "Mr. Stub";
var entity = new EntityClass(stubEntityRepository.Object);
// Act
var name = entity.GetNameWithPrefix(12);
// Assert
Assert.AreEqual(EXPECTED_NAME_WITH_PREFIX, name);
}
}
public class EntityClass
{
private IEntityRepository _entityRepository;
public EntityClass(IEntityRepository entityRepository)
{
this._entityRepository = entityRepository;
}
public string Name { get; set; }
public string GetNameWithPrefix(int id)
{
string name = string.Empty;
if (id > 0)
{
name = this._entityRepository.GetName(id);
}
return "Mr. " + name;
}
}
public interface IEntityRepository
{
string GetName(int id);
}
public class EntityRepository:IEntityRepository
{
public string GetName(int id)
{
// Code to connect to DB and get name based on Id
return "NameFromDb";
}
}
}
Point de vue des tests de Stub and Mock:
Stub est une implémentation factice effectuée par l'utilisateur de manière statique , c'est-à-dire que Stub écrit le code d'implémentation. Il ne peut donc pas gérer la définition de service et la condition dynamique. Normalement, cela se fait dans le cadre JUnit sans utiliser le cadre de simulation.
Mock est également une implémentation factice, mais son implémentation est effectuée de manière dynamique en utilisant des frameworks Mocking comme Mockito. Nous pouvons donc gérer la définition des conditions et des services de manière dynamique, c'est-à-dire que les mocks peuvent être créés dynamiquement à partir du code lors de l'exécution. Donc, en utilisant mock, nous pouvons implémenter Stubs dynamiquement.
Plus des réponses utiles, l'un des points les plus puissants de l'utilisation de Mocks than Subs
Si le collaborateur [dont le code principal en dépend] n'est pas sous notre contrôle (par exemple à partir d'une bibliothèque tierce),
dans ce cas, le stub est plus difficile à écrire qu'à se moquer .
J'ai utilisé des exemples de python dans ma réponse pour illustrer les différences.
Stub - Stubbing est une technique de développement logiciel utilisée pour implémenter des méthodes de classes au début du cycle de vie du développement. Ils sont couramment utilisés comme espaces réservés pour l'implémentation d'une interface connue, où l'interface est finalisée ou connue mais l'implémentation n'est pas encore connue ou finalisée. Vous commencez avec des stubs, ce qui signifie simplement que vous écrivez uniquement la définition d'une fonction et laissez le code réel pour plus tard. L'avantage est que vous n'oublierez pas les méthodes et que vous pourrez continuer à penser à votre design tout en le voyant dans le code. Vous pouvez également demander à votre talon de renvoyer une réponse statique afin que la réponse puisse être utilisée par d'autres parties de votre code immédiatement. Les objets stub fournissent une réponse valide, mais c'est statique, quelle que soit l'entrée que vous transmettez, vous obtiendrez toujours la même réponse:
class Foo(object):
def bar1(self):
pass
def bar2(self):
#or ...
raise NotImplementedError
def bar3(self):
#or return dummy data
return "Dummy Data"
Moquer objets sont utilisés dans les scénarios de test fictifs, ils valident que certaines méthodes sont appelées sur ces objets. Les objets fantaisie sont des objets simulés qui imitent le comportement d'objets réels de manière contrôlée. Vous créez généralement un objet factice pour tester le comportement d'un autre objet. Les simulations nous permettent de simuler des ressources qui ne sont pas disponibles ou trop lourdes pour les tests unitaires.
mymodule.py:
import os
import os.path
def rm(filename):
if os.path.isfile(filename):
os.remove(filename)
test.py:
from mymodule import rm
import mock
import unittest
class RmTestCase(unittest.TestCase):
@mock.patch('mymodule.os')
def test_rm(self, mock_os):
rm("any path")
# test that rm called os.remove with the right parameters
mock_os.remove.assert_called_with("any path")
if __name__ == '__main__':
unittest.main()
Il s'agit d'un exemple très basique qui exécute simplement rm et affirme le paramètre avec lequel il a été appelé. Vous pouvez utiliser une maquette avec des objets non seulement des fonctions comme indiqué ici, et vous pouvez également renvoyer une valeur afin qu'un objet maquette puisse être utilisé pour remplacer un stub pour le test.
Plus sur unittest.mock , notez qu'en python 2.x, le mock n'est pas inclus dans unittest mais est un module téléchargeable qui peut être téléchargé via pip (pip install mock).
J'ai également lu "The Art of Unit Testing" de Roy Osherove et je pense que ce serait génial si un livre similaire était écrit en utilisant des exemples Python et Python. Si quelqu'un connaît un tel livre, merci de le partager. À votre santé :)
Un stub est un faux objet construit à des fins de test. Une maquette est un talon qui enregistre si les appels attendus se sont effectivement produits.
Un stub est une fonction vide qui est utilisée pour éviter les exceptions non gérées pendant les tests:
function foo(){}
Une maquette est une fonction artificielle utilisée pour éviter les dépendances du système d'exploitation, de l'environnement ou du matériel pendant les tests:
function foo(bar){ window = this; return window.toString(bar); }
En termes d'affirmations et d'état:
Références
beaucoup de réponses valables là-haut, mais je pense qu'il vaut la peine de mentionner cette forme oncle bob: https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html
la meilleure explication jamais avec des exemples!
Une maquette est à la fois un objet technique et fonctionnel .
La maquette est technique . Il est en effet créé par une bibliothèque fictive (EasyMock, JMockit et plus récemment Mockito sont connus pour ceux-ci) grâce à la génération de code octet .
L'implémentation fictive est générée de manière à pouvoir l' instrumenter pour renvoyer une valeur spécifique lorsqu'une méthode est invoquée, mais aussi d'autres choses telles que la vérification qu'une méthode fictive a été invoquée avec certains paramètres spécifiques (contrôle strict) ou quels que soient les paramètres ( pas de contrôle strict).
Instanciation d'une simulation:
@Mock Foo fooMock
Enregistrer un comportement:
when(fooMock.hello()).thenReturn("hello you!");
Vérification d'une invocation:
verify(fooMock).hello()
Ce n'est clairement pas le moyen naturel d'instancier / remplacer la classe / le comportement Foo. C'est pourquoi je me réfère à un aspect technique.
Mais la maquette est également fonctionnelle car c'est une instance de la classe que nous devons isoler du SUT. Et avec des comportements enregistrés dessus, nous pourrions l'utiliser dans le SUT de la même manière que nous le ferions avec un talon.
Le stub n'est qu'un objet fonctionnel : c'est une instance de la classe que nous devons isoler du SUT et c'est tout. Cela signifie que la classe de stub et tous les agencements de comportements nécessaires lors de nos tests unitaires doivent être définis explicitement.
Par exemple, pour stub, hello()
il faudrait sous- Foo
classer la classe (ou implémenter son interface, il l'a) et remplacer hello()
:
public class HelloStub extends Hello{
public String hello {
return "hello you!";
}
}
Si un autre scénario de test nécessite un autre retour de valeur, nous aurions probablement besoin de définir une manière générique de définir le retour:
public class HelloStub extends Hello{
public HelloStub(String helloReturn){
this.helloReturn = helloReturn;
}
public String hello {
return helloReturn;
}
}
Autre scénario: si j'avais une méthode d'effet secondaire (pas de retour) et que je vérifierais que cette méthode a été invoquée, j'aurais probablement dû ajouter un booléen ou un compteur dans la classe stub pour compter combien de fois la méthode a été invoquée.
Conclusion
Le stub nécessite souvent beaucoup de surcharge / code pour écrire pour votre test unitaire. Ce que la simulation empêche grâce aux fonctionnalités d'enregistrement / de vérification prêtes à l'emploi.
C'est pourquoi de nos jours, l'approche stub est rarement utilisée dans la pratique avec l'avènement d'excellentes bibliothèques fictives.
À propos de l'article de Martin Fowler: Je ne pense pas être un programmeur "moqueur" lorsque j'utilise des simulacres et j'évite les talons.
Mais j'utilise la simulation quand c'est vraiment nécessaire (dépendances gênantes) et je privilégie les tests de découpage et les tests de mini-intégration lorsque je teste une classe avec des dépendances dont la simulation serait une surcharge.