Meilleures pratiques de développement piloté par les tests en utilisant C# et RhinoMocks [fermé]

Afin d'aider mon équipe à écrire du code testable, je suis venu avec cette liste simple des meilleures pratiques pour rendre notre base de code C# plus testable. (Certains points se réfèrent aux limites de Rhino Mocks, un cadre moqueur pour C#, mais les règles peuvent également s'appliquer de manière plus générale.) Quelqu'un a-t-il des meilleures pratiques qu'il suit?

Pour maximiser la testabilité du code, suivez ces règles:

  1. Écrivez d'abord le test, puis le code. raison: cela garantit que vous écrivez du code testable et que chaque ligne de code obtient des tests écrits pour cela.

  2. Classes de conception utilisant l'injection de dépendance. raison: vous ne pouvez pas vous moquer ou tester ce qui ne peut pas être vu.

  3. Séparez le code de L'interface utilisateur de son comportement en utilisant Model-View-Controller ou Model-View-Presenter. raison: permet de tester la logique métier alors que les parties qui ne peuvent pas être testées (L'interface utilisateur) sont minimisées.

  4. N'écrivez pas de méthodes statiques ou classe. raison: les méthodes statiques sont difficiles, voire impossibles à isoler et Rhino se moque d'elles.

  5. Programmez les interfaces, pas les classes. Raison: l'Utilisation d'interfaces clarifie les relations entre les objets. Une interface doit définir un service dont un objet a besoin de son environnement. En outre, les interfaces peuvent être facilement moquées à l'aide de Rhino Mocks et d'autres frameworks moqueurs.

  6. Isoler les dépendances externes. raison: Les dépendances externes non résolues ne peuvent pas être testées.

  7. Marquez comme virtuel les méthodes que vous avez l'intention de moquer. raison: Rhino Mocks est incapable de se moquer des méthodes non virtuelles.

85
demandé sur Kevin Albrecht 2008-09-24 02:03:18

7 réponses

Certainement une bonne liste. Voici quelques réflexions à ce sujet:

Écrivez d'abord le test, puis le code.

Je suis d'accord, à un niveau élevé. Mais, je serais plus précis: "écrivez d'abord un test, puis écrivez juste assez de code pour passer le test, et répétez."Sinon, j'aurais peur que mes tests unitaires ressemblent davantage à des tests d'intégration ou d'acceptation.

Classes de conception utilisant l'injection de dépendance.

D'accord. Lorsqu'un objet crée ses propres dépendances, vous n'avez aucun contrôle sur eux. L'Inversion de L'Injection de contrôle / dépendance vous donne ce contrôle, vous permettant d'isoler l'objet testé avec des mocks/stubs/etc. C'est ainsi que vous testez les objets isolément.

Séparez le code de L'interface utilisateur de son comportement en utilisant Model-View-Controller ou Model-View-Presenter.

D'accord. Notez que même le présentateur / contrôleur peut être testé en utilisant DI / IoC, en lui remettant une vue tronquée/moquée et modèle. Consultez présentateur premier TDD pour plus à ce sujet.

N'écrivez pas de méthodes ou de classes statiques.

Pas sûr que je suis d'accord avec celui-ci. Il est possible de tester une méthode/classe statique sans utiliser de mocks. Donc, peut-être que C'est l'une de ces règles spécifiques de Rhino que vous avez mentionnées.

Programmez les interfaces, pas les classes.

Je suis d'accord, mais pour une raison légèrement différente. Les Interfaces fournissent beaucoup de flexibilité pour le développeur de logiciels-au-delà de la simple prise en charge de divers frameworks d'objets fictifs. Par exemple, il n'est pas possible de prendre en charge DI correctement sans interfaces.

Isoler les dépendances externes.

D'accord. Masquer les dépendances externes derrière votre propre façade ou adaptateur (le cas échéant) avec une interface. Cela vous permettra d'isoler votre logiciel de la dépendance externe, que ce soit un service web, une file d'attente, une base de données ou autre chose. C'est surtout important lorsque votre équipe ne contrôle pas la dépendance (alias externe).

Marquez comme virtuel les méthodes que vous avez l'intention de moquer.

C'est une limitation des railleries de Rhino. Dans un environnement qui préfère les talons codés à la main sur un framework d'objet fictif, cela ne serait pas nécessaire.

Et, quelques nouveaux points à considérer:

Utilisez des modèles de conception créative. Cela aidera avec DI, mais il vous permet également d'isoler ce code et le tester indépendamment des autres logiques.

Ecrire Des tests en utilisant La technique D'arrangement/acte/Assert de Bill Wake . Cette technique montre très clairement quelle configuration est nécessaire, ce qui est réellement testé et ce qui est attendu.

N'ayez pas peur de rouler vos propres objets fantaisie/stubs. souvent, vous constaterez que l'utilisation de frameworks d'objets fictifs rend vos tests incroyablement difficiles à lire. En roulant votre propre, vous aurez un contrôle complet sur votre mocks / stubs,et vous serez en mesure de garder vos tests lisibles. (Voir point précédent.)

Évitez la tentation de refactoriser la duplication de vos tests unitaires en classes de base abstraites ou en méthodes d'installation/démontage.{[5] } cela cache le code de configuration/nettoyage du développeur essayant de grok le test unitaire. Dans ce cas, la clarté de chaque test individuel est plus importante que la refactorisation de la duplication.

Mettre En Œuvre Une Intégration Continue. Enregistrement votre code sur chaque "barre verte."Construisez votre logiciel et exécutez votre suite complète de tests unitaires à chaque enregistrement. (Bien sûr, ce n'est pas une pratique de codage, en soi; mais c'est un outil incroyable pour garder votre logiciel propre et entièrement intégrée.)

57
répondu aridlehoover 2008-09-24 05:32:30

Si vous travaillez avec. Net 3.5, vous pouvez regarder dans la bibliothèque mocking MOQ - elle utilise des arbres d'expression et des lambdas pour supprimer l'idiome d'enregistrement-réponse non intuitif de la plupart des autres bibliothèques mocking.

Consultez ce quickstart pour voir à quel point vos cas de test deviennent plus intuitifs, voici un exemple simple:

// ShouldExpectMethodCallWithVariable
int value = 5;
var mock = new Mock<IFoo>();

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2);

Assert.AreEqual(value * 2, mock.Object.Duplicate(value));
10
répondu zadam 2015-12-28 21:55:17

Connaître la différence entre faux, mocks et stubs et quand utiliser chacun.

Évitez de trop spécifier les interactions à l'aide de mocks. Cela rend les tests fragiles .

6
répondu Hamish Smith 2008-09-24 05:42:26

Ceci est un poste très utile!

J'ajouterais qu'il est toujours important de comprendre le contexte et le système testés (SUT). Suivre les principes de TDD à la lettre est beaucoup plus facile lorsque vous écrivez un nouveau code dans un environnement où le code existant suit les mêmes principes. Mais lorsque vous écrivez un nouveau code dans un environnement non existant TDD, vous constatez que vos efforts TDD peuvent rapidement dépasser vos estimations et vos attentes.

Pour certains d'entre vous, qui vivent dans un le monde entièrement académique, les délais et la livraison peuvent ne pas être importants, mais dans un environnement où le logiciel est de l'argent, il est essentiel d'utiliser efficacement vos efforts de TDD.

TDD est fortement soumis à la Loi de diminution du rendement Marginal. En bref, vos efforts vers TDD sont de plus en plus précieux jusqu'à ce que vous atteigniez un point de rendement maximum, après quoi, le temps investi dans TDD a de moins en moins de valeur.

J'ai tendance à croire que la valeur primaire de TDD est dans la limite (blackbox) ainsi que dans des tests occasionnels de whitebox des zones critiques du système.

3
répondu 2009-05-06 02:23:23

La véritable raison de la programmation par rapport aux interfaces n'est pas de faciliter la vie de Rhino, mais de clarifier les relations entre les objets dans le code. Une interface doit définir un service dont un objet a besoin de son environnement. Une classe fournit une implémentation particulière de ce service. Lisez le livre "Object Design" de Rebecca Wirfs-Brock sur les rôles, les responsabilités et les collaborateurs.

2
répondu Steve Freeman 2009-09-06 19:45:38

Bonne liste. Une des choses que vous pourriez vouloir établir - et je ne peux pas vous donner beaucoup de conseils puisque je commence juste à y penser moi-même-est quand une classe devrait être dans une bibliothèque différente, un espace de noms, des espaces de noms imbriqués. Vous pourriez même vouloir comprendre une liste de bibliothèques et d'espaces de noms à l'avance et mandater que l'équipe doit rencontrer et décider de fusionner deux / Ajouter un nouveau.

Oh, juste pensé à quelque chose que je fais que vous pourriez vouloir aussi. J'ai généralement un bibliothèque de tests unitaires avec un montage de test par stratégie de classe où chaque test va dans un espace de noms correspondant. J'ai aussi tendance à avoir une autre bibliothèque de tests (tests d'intégration?) qui est dans un style plus BDD . Cela me permet d'écrire des tests pour spécifier ce que la méthode devrait faire ainsi que ce que l'application devrait faire globalement.

1
répondu George Mauer 2008-09-24 00:06:29

Voici un autre que j'ai pensé que j'aime faire.

Si vous prévoyez d'exécuter des tests à partir de l'interface graphique de test unitaire par opposition à from TestDriven.Net ou NAnt alors j'ai trouvé plus facile de définir le type de projet de test unitaire sur l'application console plutôt que sur la bibliothèque. Cela vous permet d'exécuter des tests manuellement et de les parcourir en mode débogage (ce qui précède TestDriven.Net peut réellement faire pour vous).

Aussi, j'aime toujours avoir un projet de terrain de jeu ouvert pour tester les bits de code et d'idées que je ne connais pas. Cela ne doit pas être vérifié dans le contrôle de source. Mieux encore, il devrait être dans un référentiel de contrôle de source séparé sur la machine du développeur uniquement.

0
répondu George Mauer 2008-09-24 13:27:15