Les tests unitaires doivent-ils être écrits pour les getter et les setters?

141

Sommes-nous censés écrire des tests pour nos getters et setters ou est-ce exagéré?

Hector421
la source
Je ne pense pas. Vous ne devez pas écrire de cas de test pour getter / setter.
Harry Joy
1
Je suppose que vous voulez dire Java? C'est une question particulièrement aiguë pour Java, encore moins pour les langages plus modernes.
skaffman
@skaffman Quelles langues modernes n'ont pas de propriétés? Bien sûr, les langages comme Java exigent qu'ils soient des corps de méthode complets, mais cela ne le rend pas logique différent de C #.
Claus Jørgensen
2
@Claus: Il n'a pas dit propriétés, il a dit getters et setters. En java, vous les écrivez manuellement, dans d'autres langues, vous obtenez un meilleur support.
skaffman
2
On pourrait se demander pourquoi il y a des getters et des setters .
Raedwald

Réponses:

181

Je dirais non.

@Will a dit que vous devriez viser une couverture de code à 100%, mais à mon avis, c'est une distraction dangereuse. Vous pouvez écrire des tests unitaires qui ont une couverture de 100%, sans rien tester.

Les tests unitaires sont là pour tester le comportement de votre code, de manière expressive et significative, et les getters / setters ne sont qu'un moyen pour atteindre une fin. Si vos tests utilisent les getters / setters pour atteindre leur objectif de tester la fonctionnalité «réelle», alors c'est assez bien.

Si, d'un autre côté, vos getters et setters font plus que simplement obtenir et définir (c'est-à-dire que ce sont des méthodes bien complexes), alors oui, ils devraient être testés. Mais n'écrivez pas un cas de test unitaire juste pour tester un getter ou des setters, c'est une perte de temps.

skaffman
la source
7
Viser une couverture de code à 100% est ridicule. Vous finirez par couvrir du code qui n'est pas exposé au public et / ou du code sans complexité. Il vaut mieux laisser ces choses pour l'autogénération, mais même les tests autogénérés sont assez inutiles. Il est préférable de se concentrer sur les tests importants, comme les tests d'intégration.
Claus Jørgensen
7
@Claus: Je suis d'accord, sauf pour le fait de se concentrer sur les tests d'intégration. Les tests unitaires sont essentiels à une bonne conception et complètent les tests d'intégration.
skaffman
5
Je pense qu'une couverture de code à 100% lorsque toute la suite de tests est exécutée est un bon objectif. Les setters de propriété getters et autres codes sont exécutés dans le cadre de tests plus importants. Les derniers éléments à couvrir sont probablement la gestion des erreurs. Et si la gestion des erreurs n'est pas couverte par les tests unitaires, elle ne le sera jamais. Voulez-vous vraiment expédier un produit contenant du code qui n'a jamais été exécuté?
Anders Abel
J'aurais tendance à ne pas être d'accord avec ce conseil. Oui, les getters et les setters sont simples, mais ils sont aussi étonnamment faciles à gâcher. Quelques lignes de tests simples et vous savez qu'ils fonctionnent et continuent de fonctionner.
Charles
Vous testez peut-être des setters inutiles que vous (ou un outil) avez créés juste pour avoir un POJO «arrondi». Vous pourriez être par instance en utilisant Gson.fromJson pour "gonfler" POJOS (aucun setters nécessaire). Dans ce cas, mon choix est de supprimer les setters inutilisés.
Alberto Gaona
36

Roy Osherove dans son célèbre livre `` The Art Of Unit Testing '' dit:

Les propriétés (getters / setters en Java) sont de bons exemples de code qui ne contiennent généralement aucune logique et ne nécessitent pas de test. Mais attention: une fois que vous ajoutez une vérification à l'intérieur de la propriété, vous voudrez vous assurer que la logique est testée.

Azhidkov
la source
1
Compte tenu du peu de temps qu'il faut pour les tester et de la probabilité qu'un comportement soit ajouté, je ne vois pas pourquoi. Je soupçonne que s'il est pressé, il pourrait répondre "bien, je suppose pourquoi pas".
Traîneau
1
Les premiers livres importants ont souvent de mauvais conseils. J'ai vu tellement de cas où les gens lancent rapidement des getters et des setters et font des erreurs parce qu'ils coupent et collent ou oublient cela. ou ceci-> ou quelque chose comme ça. Ils sont faciles à tester et devraient certainement être testés.
Charles
35

Un OUI retentissant avec TDD


Remarque : cette réponse continue de recevoir des votes positifs, bien que potentiellement un mauvais conseil. Pour comprendre pourquoi, jetez un œil à sa petite sœur ci-dessous.


Bien controversé, mais je dirais que quiconque répond «non» à cette question manque un concept fondamental du TDD.

Pour moi, la réponse est un oui retentissant si vous suivez TDD. Si ce n'est pas le cas, non est une réponse plausible.

Le DDD dans TDD

Le TDD est souvent cité comme présentant les principaux avantages.

  • La défense
    • S'assurer que le code peut changer mais pas son comportement .
    • Cela permet la pratique toujours aussi importante du refactoring .
    • Vous gagnez ce TDD ou pas.
  • Conception
    • Vous spécifiez ce que quelque chose doit faire, comment il doit se comporter avant de l'implémenter .
    • Cela signifie souvent des décisions de mise en œuvre plus éclairées .
  • Documentation
    • La suite de tests doit servir de documentation de spécification (exigences).
    • L'utilisation de tests à cette fin signifie que la documentation et la mise en œuvre sont toujours dans un état cohérent - un changement dans l'un signifie un changement dans l'autre. Comparez avec les exigences de conservation et la conception sur un document Word séparé.

Séparer la responsabilité de la mise en œuvre

En tant que programmeurs, il est terriblement tentant de considérer les attributs comme quelque chose d'important et les getters et setter comme une sorte de surcharge.

Mais les attributs sont un détail d'implémentation, tandis que les setters et les getters sont l'interface contractuelle qui fait réellement fonctionner les programmes.

Il est bien plus important d'épeler qu'un objet doit:

Permettre à ses clients de changer son état

et

Permettre à ses clients d'interroger son état

puis comment cet état est réellement stocké (pour lequel un attribut est le moyen le plus courant, mais pas le seul).

Un test tel que

(The Painter class) should store the provided colour

est important pour la partie documentation de TDD.

Le fait que l'implémentation éventuelle soit triviale (attribut) et ne comporte aucun avantage de défense devrait vous être inconnu lorsque vous écrivez le test.

Le manque d'ingénierie aller-retour ...

L'un des problèmes clés dans le monde du développement de systèmes est le manque d' ingénierie aller-retour 1 - le processus de développement d'un système est fragmenté en sous-processus disjoints dont les artefacts (documentation, code) sont souvent incohérents.

1 Brodie, Michael L. "John Mylopoulos: coudre des graines de modélisation conceptuelle." Modélisation conceptuelle: fondations et applications. Springer Berlin Heidelberg, 2009. 1-9.

... et comment TDD le résout

C'est la partie documentation de TDD qui garantit que les spécifications du système et son code sont toujours cohérents.

Concevez d'abord, implémentez plus tard

Dans TDD, nous écrivons d'abord les tests d'acceptation échoués, puis nous écrivons ensuite le code qui les laisse passer.

Dans le BDD de niveau supérieur, nous écrivons d'abord des scénarios, puis nous les faisons passer.

Pourquoi devriez-vous exclure les setters et les getter?

En théorie, il est parfaitement possible dans TDD qu'une personne écrive le test et qu'une autre implémente le code qui le fait passer.

Alors demandez-vous:

La personne qui écrit les tests pour une classe doit-elle mentionner getters et setter?

Puisque les getters et les setters sont une interface publique vers une classe, la réponse est évidemment oui , ou il n'y aura aucun moyen de définir ou d'interroger l'état d'un objet. Cependant , la façon de le faire n'est pas nécessairement de tester chaque méthode isolément, voir mon autre réponse pour en savoir plus.

De toute évidence, si vous écrivez d'abord le code, la réponse n'est peut-être pas aussi claire.

Izhaki
la source
2
Ce n'est qu'un côté de la médaille. Pensez à ce que vous auriez pu faire au lieu de tester juste pour le plaisir de tester. Comme Kent Beck l'a dit, vous êtes payé pour le code de travail, pas pour les tests de travail.
Georgii Oleinikov
@GeorgiiOleinikov Avez-vous lu mon autre réponse ci-dessous? Cela correspond à peu près à votre point de vue.
Izhaki
21

tl; dr: Oui, vous devriez , et avec OpenPojo, c'est trivial.

  1. Vous devriez faire une validation dans vos getters et setters donc vous devriez tester cela. Par exemple, setMom(Person p)ne devrait pas permettre de placer quelqu'un plus jeune qu'eux comme mère.

  2. Même si vous ne faites rien de tout cela maintenant, il y a de fortes chances que vous le fassiez à l'avenir, alors ce sera un bon pour l'analyse de régression. Si vous voulez autoriser les mères à nullvous faire un test, si quelqu'un change cela plus tard, cela renforcera vos hypothèses.

  3. Un bogue commun est void setFoo( Object foo ){ foo = foo; }là où il devrait être void setFoo( Object foo ){ this.foo = foo; }. (Dans le premier cas, le paramètre dansfoo lequel on écrit est le paramètre et non le foochamp de l' objet ).

  4. Si vous renvoyez un tableau ou une collection, vous devriez tester si le getter va effectuer ou non des copies défensives des données passées au setter avant de retourner.

  5. Sinon, si vous avez les setters / getters les plus basiques, alors les tests unitaires ajouteront peut-être environ 10 minutes au maximum par objet, alors quelle est la perte? Si vous ajoutez un comportement, vous avez déjà un test de squelette et vous obtenez ce test de régression gratuitement. Si vous utilisez Java, vous n'avez aucune excuse puisqu'il existe OpenPojo . Il existe un ensemble de règles que vous pouvez activer, puis analyser l'ensemble de votre projet avec elles pour vous assurer qu'elles sont appliquées de manière cohérente dans votre code.

D'après leurs exemples :

final PojoValidator pojoValidator = new PojoValidator();

//create rules
pojoValidator.addRule( new NoPublicFieldsRule  () );
pojoValidator.addRule( new NoPrimitivesRule    () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );

//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester            () );
pojoValidator.addTester( new GetterTester            () );

//test all the classes
for(  PojoClass  pojoClass :  PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() )  )
    pojoValidator.runValidation( pojoClass );
Traîneau
la source
11
Je sais que c'est vieux maintenant, mais: DEVRIEZ-VOUS faire la validation dans vos getters et setters? J'avais l'impression que setMom devrait faire ce qu'il dit. S'il est en cours de validation, ne devrait-il pas être validateAndSetMom? Ou mieux, le code de validation ne devrait-il pas exister AILLEURS qu'un simple objet? Qu'est-ce que j'oublie ici?
ndtreviv
14
Oui, vous devriez toujours valider vos entrées. Si ce n'est pas le cas, pourquoi ne pas simplement utiliser une variable publique? Cela devient la même chose. L'avantage de l'utilisation de setters par rapport aux variables est qu'il vous permet de vous assurer que votre objet n'est jamais invalide, comme l'âge étant un nombre négatif. Si vous ne faites pas cela dans l'objet, vous le ferez ailleurs (comme un objet divin "service" ) et ce n'est pas vraiment OOP à ce stade.
Traîneau
1
Eh bien, c'est probablement le point culminant de ma semaine. Je vous remercie.
Traîneau du
19

Oui, mais pas toujours de manière isolée

Permettez-moi d'élaborer:

Qu'est-ce qu'un test unitaire?

De Travailler efficacement avec l'ancien code 1 :

Le terme test unitaire a une longue histoire dans le développement de logiciels. La plupart des conceptions des tests unitaires ont en commun l'idée qu'il s'agit de tests isolés des composants individuels du logiciel. Quels sont les composants? La définition varie, mais dans les tests unitaires, nous nous intéressons généralement aux unités de comportement les plus atomiques d'un système. Dans le code procédural, les unités sont souvent des fonctions. Dans le code orienté objet, les unités sont des classes.

Notez qu'avec la POO, où vous trouvez des getters et des setters, l'unité est la classe , pas nécessairement les méthodes individuelles .

Qu'est-ce qu'un bon test?

Toutes les exigences et tous les tests suivent la forme de la logique Hoare :

{P} C {Q}

Où:

  • {P}est la condition préalable ( donnée )
  • Cest la condition de déclenchement ( quand )
  • {Q}est la postcondition ( alors )

Puis vient la maxime:

Tester le comportement, pas la mise en œuvre

Cela signifie que vous ne devez pas tester comment Créalise la post-condition, vous devez vérifier que {Q}c'est le résultat de C.

Quand il s'agit de POO, Cc'est une classe. Vous ne devriez donc pas tester les effets internes, uniquement les effets externes.

Pourquoi ne pas tester les getters et setters de haricots isolément

Les getters et les setters peuvent impliquer une certaine logique, mais tant que cette logique n'a pas d'effet externe - ce qui en fait des accesseurs de haricots 2 ) un test devra regarder à l'intérieur de l'objet et par cela non seulement violer l'encapsulation mais aussi tester l'implémentation.

Vous ne devriez donc pas tester les getters et les setters de haricots de manière isolée. C'est mauvais:

Describe 'LineItem class'

    Describe 'setVAT()'

        it 'should store the VAT rate'

            lineItem = new LineItem()
            lineItem.setVAT( 0.5 )
            expect( lineItem.vat ).toBe( 0.5 )

Même si setVATcela lèverait une exception, un test correspondant serait approprié car il y a maintenant un effet externe.

Comment tester les getters et les setters?

Il est pratiquement inutile de changer l'état interne d'un objet si un tel changement n'a aucun effet sur l'extérieur, même si cet effet survient plus tard.

Un test pour les setters et les getters devrait donc porter sur l'effet externe de ces méthodes, pas sur l'effet interne.

Par exemple:

Describe 'LineItem class'

    Describe 'getGross()'

        it 'should return the net time the VAT'

            lineItem = new LineItem()
            lineItem.setNet( 100 )
            lineItem.setVAT( 0.5 )
            expect( lineItem.getGross() ).toBe( 150 )

Vous pensez peut-être:

Attendez une seconde, nous testons getGross()ici non setVAT() .

Mais en cas de setVAT()dysfonctionnement, ce test échoue tout de même.

1 Feathers, M., 2004. Travailler efficacement avec le code hérité. Prentice Hall Professional.

2 Martin, RC, 2009. Clean code: un manuel de l'artisanat logiciel agile. Pearson Education.

Izhaki
la source
13

Bien qu'il existe des raisons justifiées pour les propriétés, il existe une croyance commune de conception orientée objet selon laquelle exposer l'état membre via Propriétés est une mauvaise conception. L'article de Robert Martin sur le principe ouvert fermé développe cela en déclarant que les propriétés encouragent le couplage et limitent par conséquent la possibilité de fermer une classe de la modification - si vous modifiez la propriété, tous les consommateurs de la classe devront également changer. Il précise que l'exposition des variables membres n'est pas nécessairement une mauvaise conception, mais peut-être simplement un style médiocre. Cependant, si les propriétés sont en lecture seule, il y a moins de risques d'abus et d'effets secondaires.

La meilleure approche que je puisse fournir pour les tests unitaires (et cela peut sembler étrange) est de rendre autant de propriétés que possible protégées ou internes. Cela empêchera le couplage tout en décourageant d'écrire des tests idiots pour les getters et les setters.

Il existe des raisons évidentes pour lesquelles des propriétés de lecture / écriture doivent être utilisées, telles que les propriétés ViewModel liées aux champs d'entrée, etc.

Plus concrètement, les tests unitaires devraient piloter les fonctionnalités via des méthodes publiques. Si le code que vous testez utilise ces propriétés, vous bénéficiez d'une couverture de code gratuite. S'il s'avère que ces propriétés ne sont jamais mises en évidence par la couverture de code, il y a une très forte possibilité que:

  1. Il vous manque des tests qui utilisent indirectement les propriétés
  2. Les propriétés sont inutilisées

Si vous écrivez des tests pour les getters et les setters, vous obtenez une fausse impression de couverture et vous ne pourrez pas déterminer si les propriétés sont réellement utilisées par le comportement fonctionnel.

bryanbcook
la source
12

Si la complexité cyclomatique du getter et / ou du setter est 1 (ce qu'ils sont généralement), alors la réponse est non, vous ne devriez pas.

Donc, à moins que vous n'ayez un SLA qui nécessite une couverture de code à 100%, ne vous inquiétez pas et concentrez-vous sur le test de l'aspect important de votre logiciel.

PS N'oubliez pas de différencier les getters et les setters, même dans des langages comme C # où les propriétés peuvent ressembler à la même chose. La complexité du setter peut être supérieure à celle du getter, et valider ainsi un test unitaire.

Claus Jørgensen
la source
4
Bien qu'il faille tester la copie défensive sur les getters
Sled
8

Une prise humoristique, mais sage: The Way of Testivus

"Ecrivez le test que vous pouvez aujourd'hui"

Tester des getters / setters peut être exagéré si vous êtes un testeur expérimenté et qu'il s'agit d'un petit projet. Cependant, si vous commencez tout juste à apprendre à tester les unités ou que ces getters / setters peuvent contenir de la logique (comme l' setMom()exemple de @ ArtB ), alors ce serait une bonne idée d'écrire des tests.

muymoo
la source
1
votre lien devrait en fait pointer vers: The Way of Testivus
JJS
2

C'est en fait un sujet récent entre mon équipe et moi. Nous visons une couverture de code à 80%. Mon équipe soutient que les getters et les setters sont automatiquement implémentés et que le compilateur génère du code de base dans les coulisses. Dans ce cas, étant donné que le code généré n'est pas intrusif, il n'a pas vraiment de sens de tester le code que le compilateur crée pour vous. Nous avons également eu cette discussion sur les méthodes asynchrones et dans ce cas, le compilateur génère tout un tas de code dans les coulisses. C'est un cas différent et quelque chose que nous testons. Réponse longue courte, abordez-la avec votre équipe et décidez vous-même si cela vaut la peine d'être testé.

De plus, si vous utilisez le rapport de couverture de code comme nous, une chose que vous pouvez faire est d'ajouter l'attribut [ExcludeFromCodeCoverage]. Notre solution a été de l'utiliser pour les modèles qui n'ont que des propriétés utilisant des getters et des setters, ou sur la propriété elle-même. De cette façon, cela n'affectera pas le% de couverture totale du code lorsque le rapport de couverture de code est exécuté, en supposant que c'est ce que vous utilisez pour calculer vos pourcentages de couverture de code. Bon test!

Devin C
la source
2

À mon avis, la couverture du code est un bon moyen de voir si vous avez manqué une fonctionnalité que vous devriez couvrir.

Lorsque vous inspectez la couverture manuellement par sa jolie coloration, on peut affirmer que les getters et les setters simples n'ont pas besoin d'être testés (bien que je le fasse toujours).

Lorsque vous ne vérifiez que le pourcentage de couverture de code sur votre projet, un pourcentage de couverture de test comme 80% n'a pas de sens. Vous pouvez tester toutes les parties non logiques et oublier certaines parties cruciales. Dans ce cas, seulement 100% signifie que vous avez testé tout votre code vital (et tout le code non logique également). Dès qu'il est à 99,9%, vous savez que vous avez oublié quelque chose.

Soit dit en passant: la couverture du code est la dernière vérification pour voir si vous avez entièrement testé (à l'unité) une classe. Mais une couverture de code à 100% ne signifie pas nécessairement que vous avez réellement testé toutes les fonctionnalités de la classe. Les tests unitaires doivent donc toujours être implémentés en suivant la logique de la classe. En fin de compte, vous exécutez une couverture pour voir si vous avez oublié quelque chose. Quand vous l'avez bien fait, vous avez atteint 100% la première fois.

Encore une chose: en travaillant récemment dans une grande banque aux Pays-Bas, j'ai remarqué que Sonar indiquait une couverture de code à 100%. Cependant, je savais qu'il manquait quelque chose. En inspectant les pourcentages de couverture de code par fichier, il a indiqué un fichier à un pourcentage inférieur. L'ensemble du pourcentage de base du code était si grand que le seul fichier ne permettait pas d'afficher le pourcentage à 99,9%. Vous voudrez peut-être faire attention à cela ...

Radboud
la source
1

J'ai fait une petite analyse de la couverture obtenue dans le code JUnit lui-même .

Une catégorie de code non couvert est «trop simple à tester» . Cela inclut de simples getters et setters, que les développeurs de JUnit ne testent pas .

D'autre part, JUnit n'a aucune méthode (non obsolète) de plus de 3 lignes qui n'est couverte par aucun test.

avandeursen
la source
1

Je dirais: OUI Les erreurs dans les méthodes getter / setter peuvent se faufiler silencieusement et provoquer des bugs horribles.

J'ai écrit une bibliothèque pour faciliter cela et d'autres tests. La seule chose que vous devez écrire dans vos tests JUnit est la suivante:

        assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
            new EmptyCollectionCheck(), new GetterIsSetterCheck(),
            new HashcodeAndEqualsCheck(), new PublicVariableCheck())));

-> https://github.com/Mixermachine/base-test

Aaron Dietz
la source
0

Oui, surtout si l'élément à obtenir est un objet d'une classe sous-classée à partir d'une classe abstraite. Votre IDE peut ou non vous alerter qu'une certaine propriété n'a pas été initialisée.

Et puis certains tests apparemment sans rapport avec a NullPointerExceptionet il vous faut un certain temps pour comprendre qu'une propriété gettable n'est en fait pas là pour arriver en premier lieu.

Bien que ce ne soit toujours pas aussi grave que de découvrir le problème de la production.

Ce peut être une bonne idée de vous assurer que toutes vos classes abstraites ont des constructeurs. Sinon, le test d'un getter peut vous alerter d'un problème.

En ce qui concerne les getters et les setters de primitives, la question pourrait être: est-ce que je teste mon programme ou est-ce que je teste la JVM ou le CLR? De manière générale, la JVM n'a pas besoin d'être testée.

Alonso del Arte
la source