Je me demandais comment tester les classes abstraites unitaires et les classes qui étendent les classes abstraites.
Dois-je tester la classe abstraite en l'étendant, en supprimant les méthodes abstraites, puis en testant toutes les méthodes concrètes? Ensuite, ne testez que les méthodes que je remplace et testez les méthodes abstraites dans les tests unitaires pour les objets qui étendent ma classe abstraite?
Dois-je avoir un cas de test abstrait qui peut être utilisé pour tester les méthodes de la classe abstraite et étendre cette classe dans mon cas de test pour les objets qui étendent la classe abstraite?
Notez que ma classe abstraite a quelques méthodes concrètes.
la source
Les classes de base abstraites sont utilisées de deux manières.
Vous spécialisez votre objet abstrait, mais tous les clients utiliseront la classe dérivée via son interface de base.
Vous utilisez une classe de base abstraite pour éliminer la duplication au sein des objets de votre conception, et les clients utilisent les implémentations concrètes via leurs propres interfaces.!
Solution pour 1 - Modèle de stratégie
Si vous avez la première situation, vous avez en fait une interface définie par les méthodes virtuelles dans la classe abstraite que vos classes dérivées implémentent.
Vous devriez envisager d'en faire une véritable interface, de changer votre classe abstraite pour qu'elle soit concrète, et de prendre une instance de cette interface dans son constructeur. Vos classes dérivées deviennent alors des implémentations de cette nouvelle interface.
Cela signifie que vous pouvez désormais tester votre classe précédemment abstraite à l'aide d'une instance fictive de la nouvelle interface et de chaque nouvelle implémentation via l'interface désormais publique. Tout est simple et testable.
Solution pour 2
Si vous avez la deuxième situation, alors votre classe abstraite fonctionne comme une classe auxiliaire.
Jetez un œil aux fonctionnalités qu'il contient. Voyez si une partie peut être poussée sur les objets qui sont manipulés pour minimiser cette duplication. S'il vous reste encore quelque chose, envisagez d'en faire une classe auxiliaire que votre implémentation concrète prendra dans son constructeur et supprimera sa classe de base.
Cela conduit à nouveau à des classes concrètes qui sont simples et facilement testables.
Comme règle
Privilégiez un réseau complexe d'objets simples à un simple réseau d'objets complexes.
La clé du code testable extensible est de petits blocs de construction et un câblage indépendant.
Mise à jour: comment gérer les mélanges des deux?
Il est possible d'avoir une classe de base remplissant ces deux rôles ... c'est-à-dire: elle a une interface publique et a des méthodes d'assistance protégées. Si tel est le cas, vous pouvez factoriser les méthodes d'assistance en une seule classe (scénario 2) et convertir l'arbre d'héritage en un modèle de stratégie.
Si vous trouvez que certaines méthodes implémentent directement votre classe de base et que d'autres sont virtuelles, vous pouvez toujours convertir l'arbre d'héritage en un modèle de stratégie, mais je le considérerais également comme un bon indicateur que les responsabilités ne sont pas correctement alignées et peuvent besoin de refactoring.
Mise à jour 2: les classes abstraites comme tremplin (2014/06/12)
L'autre jour, j'ai eu une situation où j'ai utilisé l'abstrait, alors j'aimerais explorer pourquoi.
Nous avons un format standard pour nos fichiers de configuration. Cet outil particulier a 3 fichiers de configuration tous dans ce format. Je voulais une classe fortement typée pour chaque fichier de paramètres afin que, grâce à l'injection de dépendances, une classe puisse demander les paramètres qui lui importaient.
J'ai implémenté cela en ayant une classe de base abstraite qui sait analyser les formats de fichiers de paramètres et les classes dérivées qui ont exposé ces mêmes méthodes, mais a encapsulé l'emplacement du fichier de paramètres.
J'aurais pu écrire un "SettingsFileParser" que les 3 classes ont encapsulé, puis délégué à la classe de base pour exposer les méthodes d'accès aux données. J'ai choisi de ne pas le faire encore car cela conduirait à 3 classes dérivées avec plus de code de délégation en elles qu'autre chose.
Cependant ... à mesure que ce code évolue et que les consommateurs de chacune de ces classes de paramètres deviennent plus clairs. Chaque utilisateur de paramètres demandera certains paramètres et les transformera d'une certaine manière (comme les paramètres sont du texte, ils peuvent les envelopper dans des objets de les convertir en nombres, etc.). Dans ce cas, je vais commencer à extraire cette logique dans des méthodes de manipulation de données et à les repousser dans les classes de paramètres fortement typées. Cela conduira à une interface de niveau supérieur pour chaque ensemble de paramètres, qui finit par ne plus savoir qu'il s'agit de «paramètres».
À ce stade, les classes de paramètres fortement typées n'auront plus besoin des méthodes «getter» qui exposent l'implémentation sous-jacente des «paramètres».
À ce stade, je ne voudrais plus que leur interface publique inclue les méthodes d'accesseur des paramètres; je vais donc changer cette classe pour encapsuler une classe d'analyseur de paramètres au lieu d'en dériver.
La classe Abstract est donc: un moyen pour moi d'éviter le code de délégation pour le moment, et un marqueur dans le code pour me rappeler de changer le design plus tard. Je n'y arriverai peut-être jamais, donc ça peut vivre longtemps ... seul le code peut le dire.
Je trouve que cela est vrai avec n'importe quelle règle ... comme "pas de méthodes statiques" ou "pas de méthodes privées". Ils indiquent une odeur dans le code ... et c'est bien. Il vous permet de rechercher l'abstraction que vous avez manquée ... et vous permet de continuer à apporter de la valeur à votre client dans l'intervalle.
J'imagine des règles comme celle-ci définissant un paysage, où le code maintenable vit dans les vallées. Lorsque vous ajoutez un nouveau comportement, c'est comme une pluie tombant sur votre code. Au début, vous le placez partout où il atterrit .. puis vous refactorisez pour permettre aux forces de bonne conception de pousser le comportement jusqu'à ce que tout finisse dans les vallées.
la source
Ce que je fais pour les classes abstraites et les interfaces est le suivant: j'écris un test, qui utilise l'objet tel qu'il est concret. Mais la variable de type X (X est la classe abstraite) n'est pas définie dans le test. Cette classe de test n'est pas ajoutée à la suite de tests, mais à ses sous-classes, qui ont une méthode de configuration qui définit la variable sur une implémentation concrète de X. De cette façon, je ne duplique pas le code de test. Les sous-classes du test non utilisé peuvent ajouter plus de méthodes de test si nécessaire.
la source
Pour effectuer un test unitaire spécifiquement sur la classe abstraite, vous devez le dériver à des fins de test, tester les résultats de base.method () et le comportement prévu lors de l'héritage.
Vous testez une méthode en l'appelant alors testez une classe abstraite en l'implémentant ...
la source
Si votre classe abstraite contient une fonctionnalité concrète qui a une valeur commerciale, je la teste généralement directement en créant un double de test qui stoppe les données abstraites, ou en utilisant un cadre de simulation pour le faire pour moi. Laquelle je choisis dépend beaucoup de la nécessité ou non d'écrire des implémentations spécifiques aux tests des méthodes abstraites.
Le scénario le plus courant dans lequel je dois le faire est lorsque j'utilise le modèle de méthode de modèle , comme lorsque je construis une sorte de cadre extensible qui sera utilisé par un tiers. Dans ce cas, la classe abstraite est ce qui définit l'algorithme que je veux tester, il est donc plus logique de tester la base abstraite qu'une implémentation spécifique.
Cependant, je pense qu'il est important que ces tests se concentrent uniquement sur les implémentations concrètes de la logique métier réelle ; vous ne devez pas tester les détails d'implémentation de test de la classe abstraite, car vous vous retrouverez avec des tests fragiles.
la source
une façon consiste à écrire un cas de test abstrait qui correspond à votre classe abstraite, puis à écrire des cas de test concrets qui sous-classent votre cas de test abstrait. faites cela pour chaque sous-classe concrète de votre classe abstraite d'origine (c'est-à-dire que votre hiérarchie de cas de test reflète votre hiérarchie de classe). voir Tester une interface dans le livre de recettes junit: http://safari.informit.com/9781932394238/ch02lev1sec6 .
voir également Testcase Superclass dans les modèles xUnit: http://xunitpatterns.com/Testcase%20Superclass.html
la source
Je plaiderais contre les tests "abstraits". Je pense qu'un test est une idée concrète et n'a pas d'abstraction. Si vous avez des éléments communs, mettez-les dans des méthodes ou des classes d'assistance pour que tout le monde les utilise.
En ce qui concerne le test d'une classe de test abstraite, assurez-vous de vous demander ce que vous testez. Il existe plusieurs approches et vous devez savoir ce qui fonctionne dans votre scénario. Essayez-vous de tester une nouvelle méthode dans votre sous-classe? Faites ensuite interagir vos tests uniquement avec cette méthode. Testez-vous les méthodes de votre classe de base? Ensuite, vous aurez probablement un appareil distinct uniquement pour cette classe et testez chaque méthode individuellement avec autant de tests que nécessaire.
la source
C'est le modèle que je suis habituellement en train de configurer un faisceau pour tester une classe abstraite:
Et la version que j'utilise sous test:
Si les méthodes abstraites sont appelées alors que je ne m'y attendais pas, les tests échouent. Lors de l'organisation des tests, je peux facilement supprimer les méthodes abstraites avec des lambdas qui effectuent des assertions, lèvent des exceptions, renvoient des valeurs différentes, etc.
la source
Si les méthodes concrètes invoquent l'une des méthodes abstraites, cette stratégie ne fonctionnera pas et vous souhaitez tester chaque comportement de classe enfant séparément. Sinon, l'étendre et écraser les méthodes abstraites comme vous l'avez décrit devrait être correct, à condition que les méthodes concrètes de la classe abstraite soient découplées des classes enfants.
la source
Je suppose que vous pourriez vouloir tester la fonctionnalité de base d'une classe abstraite ... Mais vous feriez probablement mieux en étendant la classe sans remplacer aucune méthode, et en faisant un effort minimum pour les méthodes abstraites.
la source
L'une des principales motivations pour utiliser une classe abstraite est d'activer le polymorphisme dans votre application - c'est-à-dire: vous pouvez remplacer une version différente au moment de l'exécution. En fait, c'est à peu près la même chose que d'utiliser une interface, sauf que la classe abstraite fournit une plomberie commune, souvent appelée modèle de modèle .
Du point de vue des tests unitaires, il y a deux choses à considérer:
Interaction de votre classe abstraite avec les classes associées . L'utilisation d'un cadre de test simulé est idéale pour ce scénario car il montre que votre classe abstraite joue bien avec les autres.
Fonctionnalité des classes dérivées . Si vous avez écrit une logique personnalisée pour vos classes dérivées, vous devez tester ces classes isolément.
edit: RhinoMocks est un cadre de test de simulation génial qui peut générer des objets simulés lors de l'exécution en dérivant dynamiquement de votre classe. Cette approche peut vous faire économiser d'innombrables heures de classes dérivées à codage manuel.
la source
Tout d'abord, si la classe abstraite contenait une méthode concrète, je pense que vous devriez le faire en tenant compte de cet exemple
la source
Si une classe abstraite est appropriée pour votre implémentation, testez (comme suggéré ci-dessus) une classe concrète dérivée. Vos hypothèses sont correctes.
Pour éviter toute confusion future, sachez que cette classe de test concrète n'est pas un simulacre, mais un faux .
En termes stricts, une maquette est définie par les caractéristiques suivantes:
la source
Suite à la réponse de @ patrick-desjardins, j'ai implémenté le résumé et sa classe d'implémentation ainsi
@Test
que:Classe abstraite - ABC.java
Comme les classes abstraites ne peuvent pas être instanciées, mais elles peuvent être sous - classées , la classe concrète DEF.java est la suivante:
Classe @Test pour tester à la fois la méthode abstraite et non abstraite:
la source