2009-08-19 4 views
6

Je pense comprendre la définition des tests basés sur l'état/l'interaction (lire le truc de Fowler, etc.). J'ai trouvé que j'ai commencé basé sur l'état, mais j'ai fait plus d'interaction basée et je suis un peu confus sur la façon de tester certaines choses.Tests d'état/d'interaction et confusion sur le mélange (ou l'abus)

J'ai un contrôleur dans MVC et une action appelle un service de refuser un paquet:

public ActionResult Deny(int id) 
{ 
    service.DenyPackage(id); 

    return RedirectToAction("List"); 
} 

Cela me semble clair. Fournissez un service de simulation, vérifiez qu'il a été appelé correctement, terminé.

Maintenant, j'ai une action pour une vue qui permet à l'utilisateur d'associer un certificat avec un package:

public ActionResult Upload(int id) 
{ 
    var package = packageRepository.GetPackage(id); 
    var certificates = certificateRepository.GetAllCertificates(); 

    var view = new PackageUploadViewModel(package, certificates); 

    return View(view); 
} 

Ce que je suis un peu perplexe sur. Je fais des tests de style Spec (éventuellement incorrectement) donc pour tester cette méthode j'ai une classe et ensuite deux tests: vérifier que le référentiel de paquetages a été appelé, vérifier que le référentiel de certificats a été appelé. Je veux en fait qu'un tiers teste pour vérifier que le constructeur a été appelé mais n'a aucune idée de comment faire ça! J'ai l'impression que c'est complètement faux.

Donc, pour le test basé sur l'état, je passerais dans l'ID, puis tester la vue ActionResult. D'accord, c'est logique. Mais n'aurais-je pas un test sur le constructeur PackageUploadViewModel? Donc, si j'ai un test sur le constructeur, alors une partie de moi voudrait juste vérifier que j'appelle le constructeur et que le retour d'action correspond à ce que le constructeur retourne. Maintenant, une autre option que je peux penser est que j'ai un PackageUploadViewModelBuilder (ou quelque chose de tout aussi bêtement nommé) qui dépend des deux dépôts, puis je passe juste l'ID à une méthode CreateViewModel ou quelque chose. Je pourrais alors me moquer de cet objet, vérifier tout, et être heureux. Mais ... eh bien ... ça semble extravagant. Je fais quelque chose de simple ... pas simple. De plus, controller.action (id) renvoyant builder.create (id) semble ajouter une couche sans raison (le contrôleur est responsable de la construction des modèles de vue .. non?)

Je ne sais pas ... Je pense plus de tests basés sur l'état est nécessaire, mais j'ai peur que si je commence à tester les valeurs de retour, si la méthode A peut être appelée dans 8 contextes différents, je vais avoir une explosion de test avec beaucoup de répétitions. J'avais utilisé des tests basés sur l'interaction pour passer certains de ces contextes à la méthode B de sorte que tout ce que j'ai à faire est de vérifier la méthode A appelée méthode B et la méthode B testée afin que la méthode A puisse juste faire confiance à ces contextes. Les tests basés sur l'interaction construisent donc cette hiérarchie de tests, mais les tests basés sur l'état vont l'aplatir.

Je n'ai aucune idée si cela avait un sens.

Wow, c'est long ...

Répondre

5

Je pense que Roy Osherove a récemment twitté qu'en règle générale, vos tests devraient être fondés sur l'état de 95 pour cent et 5 pour cent par interaction. Je suis d'accord.

Ce qui importe le plus, c'est que votre API fasse ce que vous voulez, et que est ce que vous devez tester. Si vous testez la mécanique de la façon dont il réalise ce qu'il doit faire, vous risquez de vous retrouver avec des tests surspécifiés, ce qui vous mordra quand il s'agit de maintenabilité.

Dans la plupart des cas, vous pouvez concevoir votre API de sorte que le test basé sur l'état soit le choix naturel, parce que c'est beaucoup plus simple.

Pour examiner votre exemple de téléchargement: Est-ce important que GetPackage et GetAllCertificates aient été appelés? Est-ce vraiment le résultat attendu de la méthode Upload?

Je ne pense pas. Ma conjecture est que le but de la méthode Upload - c'est très raison pour exister - est de remplir et de servir la vue correcte. Par conséquent, les tests basés sur l'état examinent ViewResult renvoyé et son ViewModel et vérifient qu'il possède toutes les valeurs correctes. Bien sûr, étant donné que le code est actuellement affiché, vous devrez fournir des doublons de test pour packageRepository et certificateRepository, car sinon des exceptions seront levées, mais il ne semble pas important en soi que les méthodes de référentiel soient en cours. appelé.

Si vous utilisez des stubs au lieu de Mocks pour vos référentiels, vos tests ne sont plus liés aux détails d'implémentation internes. Si vous décidez plus tard de changer l'implémentation de la méthode Upload pour utiliser des instances en cache des paquets (ou autre), le Stub ne sera pas appelé, mais c'est correct car ce n'est pas important - est important contient les données attendues. Ceci est beaucoup plus préférable que d'avoir la pause de test même si toutes les données renvoyées sont comme il se doit. Il est intéressant de noter que votre exemple Deny ressemble à un exemple probant où le test basé sur l'interaction est toujours justifié, car c'est uniquement en examinant les résultats indirects que vous pouvez vérifier que la méthode a exécuté l'action correcte (la méthode DenyPackage renvoie void).

Tout cela, et plus, est très bien expliqué dans l'excellent livre xUnit Test Patterns.

+0

Yah, l'exemple Deny était super clair que je devrais tester les interactions. Le télécharger un était ce qui me déroute. J'étais sur la clôture avec les modèles de xUnit (il est apparemment énorme et j'avais peur de l'acheter et de ne pas le lire) mais le renvoi pourrait en valoir la peine. Une chose, et cela pourrait être juste un détail, mais pour tester le Upload (ce qui me fait réaliser quel nom terrible que Action est ... refactor time), je pourrais (a) créer la vue dans le test manuellement et vérifiez que les deux vues sont égales ou (b) vérifiez que chaque propriété de la vue est égale à ce qu'elle devrait être. Préférences? – anonymous

+0

@eyston: Le livre de Roy Osherove "L'art des tests unitaires" est une bonne alternative si vous avez peur que l'autre soit trop gros en ce moment - mais vous devriez toujours prévoir de le lire à une date ultérieure. –

+0

@eyston: Peu importe que vous décidiez de comparer deux vues, ou toutes leurs propriétés indépendamment, vous faites généralement une assertion logique. C'est plus facile si vous pouvez simplement comparer deux objets complexes entre eux, mais cela n'est possible que si la classe remplace Equals de la manière correcte. Que vous le vouliez ou non, vous devriez décider en fonction de la façon dont vous souhaitez modéliser l'API elle-même.En d'autres termes: ne remplacez pas Equals juste pour tester, mais si vous avez déjà écrasé Equals de la manière souhaitée, vous pouvez certainement l'utiliser pour faire des assertions. –

1

La question à poser est "si ce code a fonctionné, comment pourrais-je le dire?" Cela pourrait signifier tester certaines interactions ou certains états, cela dépend de ce qui est important.

Lors de votre premier test, le Deny change le monde en dehors de la classe cible. Cela nécessite une collaboration d'un service, tester une interaction est donc logique. Dans votre deuxième test, vous faites des requêtes sur les voisins (ne changez rien en dehors de la classe cible), donc les écraser est plus logique.

C'est pourquoi nous avons une heuristique de « Stub requêtes, actions Mock » dans http://www.mockobjects.com/book