Question

Il y a quelque temps, j'ai lu le Les moqueries ne sont pas des talons article de Martin Fowler et je dois admettre que j'ai un peu peur des dépendances externes en ce qui concerne la complexité supplémentaire, donc j'aimerais demander :

Quelle est la meilleure méthode à utiliser lors des tests unitaires ?

Est-il préférable de toujours utiliser un framework simulé pour simuler automatiquement les dépendances de la méthode testée, ou préférez-vous utiliser des mécanismes plus simples comme par exemple des talons de test ?

Était-ce utile?

La solution

Comme le dit le mantra « Optez pour la chose la plus simple qui puisse fonctionner ».

  1. Si les faux cours peuvent faire le travail, suivez-les.
  2. Si vous avez besoin d’une interface avec plusieurs méthodes à simuler, optez pour un framework simulé.

Évitez d'utiliser des simulations toujours car ils rendent les tests fragiles.Vos tests ont désormais une connaissance complexe des méthodes appelées par l'implémentation, si l'interface simulée ou votre implémentation change...vos tests échouent.C'est mauvais car vous passerez plus de temps à exécuter vos tests au lieu de simplement exécuter votre SUT. Les tests ne doivent pas être indûment liés à la mise en œuvre.
Alors utilisez votre meilleur jugement.Je préfère les moqueries quand cela m'aidera à éviter d'écrire et de mettre à jour une fausse classe avec n >> 3 méthodes.

Mise à jour Épilogue/Délibération :
(Merci à Toran Billups pour exemple d'un test moqueur.Voir ci-dessous)
Salut Doug, Eh bien, je pense que nous sommes entrés dans une autre guerre sainte – les TDDers classiques contre les TDDers moqueurs.Je pense que j'appartiens au premier.

  • Si je suis sur le test n°101 Test_ExportProductList et que je trouve que je dois ajouter un nouveau paramètre à IProductService.GetProducts().Je fais ça pour que ce test soit vert.J'utilise un outil de refactoring pour mettre à jour toutes les autres références.Maintenant, je trouve que tous les tests moqueurs appelant ce membre explosent.Ensuite, je dois revenir en arrière et mettre à jour tous ces tests – une perte de temps.Pourquoi ShouldPopulateProductsListOnViewLoadWhenPostBackIsFalse a-t-il échoué ?Était-ce parce que le code était cassé ?Au contraire, les tests sont brisés.je privilégie le un échec de test = 1 endroit à réparer.La fréquence moqueuse va à l’encontre de cela.Les talons seraient-ils meilleurs ?Si c'était le cas, j'avais un fake_class.GetProducts()..bien sûr Un endroit à changer au lieu d'une opération au fusil de chasse sur plusieurs appels Attendez-vous.En fin de compte, c'est une question de style..si vous aviez une méthode utilitaire commune MockHelper.SetupExpectForGetProducts() - cela suffirait également.mais vous verrez que c'est rare.
  • Si vous placez une bande blanche sur le nom du test, celui-ci sera difficile à lire.Une grande partie du code de plomberie pour le cadre fictif cache le test réel en cours.
  • vous oblige à apprendre cette saveur particulière d'un framework moqueur

Autres conseils

Je préfère généralement utiliser des simulations en raison des attentes.Lorsque vous appelez une méthode sur un stub qui renvoie une valeur, elle vous renvoie généralement simplement une valeur.Mais lorsque vous appelez une méthode sur une simulation, non seulement elle renvoie une valeur, mais elle renforce également l'attente que vous avez définie selon laquelle la méthode a même été appelée en premier lieu.En d’autres termes, si vous définissez une attente et n’appelez pas cette méthode, une exception est levée.Lorsque vous définissez une attente, vous dites essentiellement "Si cette méthode ne s'appelle pas, quelque chose s'est mal passé." Et l'inverse est vrai, si vous appelez une méthode sur une simulation et que vous n'avez pas établi d'attente, cela mettra une exception, disant essentiellement "Hé, que faites-vous d'appeler cette méthode quand vous ne vous y attendez pas."

Parfois, vous ne voulez pas d'attentes sur chaque méthode que vous appelez, donc certains frameworks moqueurs autoriseront des simulations "partielles" qui ressemblent à des hybrides simulation/stub, dans le sens où seules les attentes que vous définissez sont appliquées et tous les autres appels de méthode sont traités. cela ressemble plus à un stub dans la mesure où il renvoie simplement une valeur.

Cependant, un endroit valable pour utiliser les stubs auquel je peux penser est lorsque vous introduisez des tests dans du code existant.Parfois, il est simplement plus facile de créer un stub en sous-classant la classe que vous testez plutôt que de tout refactoriser pour rendre la simulation facile, voire possible.

Et à cela...

Évitez toujours d’utiliser des simulations car elles rendent les tests fragiles.Vos tests ont désormais une connaissance complexe des méthodes appelées par l'implémentation, si l'interface simulée change...vos tests échouent.Alors utilisez votre meilleur jugement..<

...Je dis que si mon interface change, mes tests feraient mieux de s'interrompre.Parce que tout l’intérêt des tests unitaires est qu’ils testent avec précision mon code tel qu’il existe actuellement.

Il est préférable d'utiliser une combinaison et vous devrez faire preuve de votre propre jugement.Voici les directives que j'utilise :

  • Si l'appel à du code externe fait partie du comportement attendu (vers l'extérieur) de votre code, cela doit être testé.Utilisez une simulation.
  • Si l'appel est vraiment un détail d'implémentation dont le monde extérieur ne se soucie pas, préférez les stubs.Cependant:
  • Si vous craignez que des implémentations ultérieures du code testé ne contournent accidentellement vos stubs et que vous souhaitez remarquer si cela se produit, utilisez une simulation.Vous associez votre test à votre code, mais c'est pour remarquer que votre stub n'est plus suffisant et que votre test doit être retravaillé.

Le deuxième type de moquerie est une sorte de mal nécessaire.En réalité, ce qui se passe ici, c'est que, que vous utilisiez un stub ou un mock, dans certains cas, vous devez vous associer à votre code plus que vous ne le souhaiteriez.Lorsque cela se produit, il est préférable d'utiliser une simulation plutôt qu'un stub uniquement parce que vous saurez quand ce couplage se rompt et que votre code n'est plus écrit comme votre test le pensait.Il est probablement préférable de laisser un commentaire dans votre test lorsque vous faites cela afin que quiconque le casse sache que son code n'est pas faux, le test l'est.

Et encore une fois, il s’agit d’une odeur de code et d’un dernier recours.Si vous constatez que vous devez le faire souvent, essayez de repenser la façon dont vous rédigez vos tests.

Cela dépend simplement du type de test que vous effectuez.Si vous effectuez des tests basés sur le comportement, vous souhaiterez peut-être une simulation dynamique afin de pouvoir vérifier qu'une certaine interaction avec votre dépendance se produit.Mais si vous effectuez des tests basés sur l'état, vous souhaiterez peut-être un talon afin de vérifier les valeurs/etc.

Par exemple, dans le test ci-dessous, vous remarquez que j'efface la vue afin de pouvoir vérifier qu'une valeur de propriété est définie (test basé sur l'état).Je crée ensuite une simulation dynamique de la classe de service afin de pouvoir m'assurer qu'une méthode spécifique est appelée pendant le test (test basé sur l'interaction/le comportement).

<TestMethod()> _
Public Sub Should_Populate_Products_List_OnViewLoad_When_PostBack_Is_False()
    mMockery = New MockRepository()
    mView = DirectCast(mMockery.Stub(Of IProductView)(), IProductView)
    mProductService = DirectCast(mMockery.DynamicMock(Of IProductService)(), IProductService)
    mPresenter = New ProductPresenter(mView, mProductService)
    Dim ProductList As New List(Of Product)()
    ProductList.Add(New Product())
    Using mMockery.Record()
        SetupResult.For(mView.PageIsPostBack).Return(False)
        Expect.Call(mProductService.GetProducts()).Return(ProductList).Repeat.Once()
    End Using
    Using mMockery.Playback()
        mPresenter.OnViewLoad()
    End Using
    'Verify that we hit the service dependency during the method when postback is false
    Assert.AreEqual(1, mView.Products.Count)
    mMockery.VerifyAll()
End Sub

Peu importe Statist contre.Interaction.Pensez aux rôles et aux relations.Si un objet collabore avec un voisin pour faire son travail, alors cette relation (telle qu'exprimée dans une interface) est candidate à des tests à l'aide de simulations.Si un objet est un objet de valeur simple avec un peu de comportement, testez-le directement.Je ne vois pas l'intérêt d'écrire des simulations (ou même des talons) à la main.C'est ainsi que nous avons tous commencé et refactorisé à partir de cela.

Pour une discussion plus longue, pensez à jeter un œil à http://www.mockobjects.com/book

Lisez la discussion de Luke Kanies sur exactement cette question dans ce billet de blog.Il fait référence un message de Jay Fields ce qui suggère même que l'utilisation de [un équivalent de ruby's/mocha's] stub_everything est préférable pour rendre les tests plus robustes.Pour citer les derniers mots de Fields :"Mocha rend aussi simple la définition d'un mock que la définition d'un stub, mais cela ne signifie pas que vous devez toujours préférer les mocks.En fait, je préfère généralement les talons et j'utilise des simulations lorsque cela est nécessaire."

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top