Est-il judicieux de mesurer la couverture conditionnelle du code Java 8?

19

Je me demande si la mesure de la couverture du code conditionnel par les outils actuels pour Java n'est pas obsolète depuis l'arrivée de Java 8. Avec Java 8 de Optionalet Streamon peut souvent éviter les branches / boucles code, ce qui le rend facile d'obtenir une couverture très élevée conditionnelle sans tester tous les chemins d'exécution possibles. Comparons l'ancien code Java avec le code Java 8:

Avant Java 8:

public String getName(User user) {
    if (user != null) {
        if (user.getName() != null) {
            return user.getName();
        }
    }
    return "unknown";
}

Il existe 3 chemins d'exécution possibles dans la méthode ci-dessus. Afin d'obtenir 100% de couverture conditionnelle, nous devons créer 3 tests unitaires.

Java 8:

public String getName(User user) {
    return Optional.ofNullable(user)
                   .map(User::getName)
                   .orElse("unknown");
}

Dans ce cas, les branches sont cachées et nous n'avons besoin que d'un test pour obtenir une couverture à 100% et peu importe le cas que nous testerons. Bien qu'il y ait toujours les mêmes 3 branches logiques qui devraient être couvertes je crois. Je pense que cela rend les statistiques de couverture conditionnelle totalement non fiables de nos jours.

Est-il judicieux de mesurer la couverture conditionnelle du code Java 8? Existe-t-il d'autres outils pour repérer le code sous-testé?

Karol Lewandowski
la source
5
Les mesures de couverture n'ont jamais été un bon moyen de déterminer si votre code est bien testé, mais simplement un moyen de déterminer ce qui n'a pas été testé. Un bon développeur réfléchira aux différents cas dans son esprit et élaborera des tests pour chacun d'entre eux - ou du moins tout ce qu'elle pense être important.
kdgregory du
3
Bien sûr, une couverture conditionnelle élevée ne signifie pas que nous avons de bons tests, mais je pense que c'est un énorme avantage de savoir quels chemins d'exécution sont découverts et c'est la question principale. Sans couverture conditionnelle, il est beaucoup plus difficile de repérer des scénarios non testés. Concernant les chemins: [utilisateur: null], [utilisateur: notnull, user.name:null], [utilisateur: notnull, user.name:notnull]. Qu'est-ce qui me manque?
Karol Lewandowski
6
Quel est le contrat de getName? Il semble que si userest nul, il devrait retourner "inconnu". Si usern'est pas nul et user.getName()est nul, il doit retourner "inconnu". Si usern'est pas nul et user.getName()n'est pas nul, il doit le renvoyer. Vous testeriez donc ces trois cas à l'unité, car c'est de cela qu'il getNames'agit. Vous semblez le faire à l'envers. Vous ne voulez pas voir les succursales et écrire les tests en fonction de celles-ci, vous voulez écrire vos tests en fonction de votre contrat et vous assurer que le contrat est rempli. C'est alors que vous avez une bonne couverture.
Vincent Savard
1
Encore une fois, je ne dis pas que la couverture prouve que mon code est parfaitement testé, mais c'était un outil extrêmement précieux me montrant ce que je n'ai pas testé avec certitude. Je pense que tester les contrats est indissociable des tests des chemins d'exécution (votre exemple est exceptionnel car il implique un mécanisme de langage implicite). Si vous n'avez pas testé le chemin, alors vous n'avez pas entièrement testé le contrat ou le contrat n'est pas entièrement défini.
Karol Lewandowski
2
Je vais répéter mon point précédent: c'est toujours le cas, sauf si vous vous limitez uniquement aux fonctionnalités de base du langage et n'appelez jamais aucune fonction qui n'a pas été instrumentée. Ce qui signifie aucune bibliothèque tierce et aucune utilisation du SDK.
kdgregory

Réponses:

4

Existe-t-il des outils qui mesurent les branches logiques qui peuvent être créées dans Java 8?

Je n'en connais aucun. J'ai essayé d'exécuter le code que vous avez via JaCoCo (alias EclEmma) juste pour être sûr, mais il montre 0 branches dans la Optionalversion. Je ne connais aucune méthode de configuration pour dire le contraire. Si vous le configuriez pour inclure également des fichiers JDK, il afficherait théoriquement des branches Optional, mais je pense qu'il serait idiot de commencer à vérifier le code JDK. Vous devez simplement supposer que c'est correct.

Je pense que le problème principal, cependant, est de réaliser que les branches supplémentaires que vous aviez avant Java 8 étaient, en un sens, des branches créées artificiellement. Le fait qu'ils n'existent plus dans Java 8 signifie simplement que vous avez maintenant le bon outil pour le travail (dans ce cas, Optional). Dans le code pré-Java 8, vous deviez écrire des tests unitaires supplémentaires afin d'avoir la certitude que chaque branche de code se comporte de manière acceptable - et cela devient un peu plus important dans les sections de code qui ne sont pas triviales comme le User/ getNameexemple.

Dans le code Java 8, vous placez plutôt votre confiance dans le JDK que le code fonctionne correctement. En l'état, vous devez traiter cette Optionalligne comme les outils de couverture de code la traitent: 3 lignes avec 0 branches. Le fait qu'il y ait d'autres lignes et branches dans le code ci-dessous est quelque chose auquel vous n'avez pas prêté attention auparavant, mais qui a existé chaque fois que vous avez utilisé quelque chose comme un ArrayListou HashMap.

Shaz
la source
2
"Qu'ils n'existent plus dans Java 8 ..." - Je ne suis pas d'accord avec cela, Java 8 est rétrocompatible ifet nullfait toujours partie du langage ;-) Il est toujours possible d'écrire du code à l'ancienne et de passer nullutilisateur ou utilisateur avec nullnom. Vos tests doivent simplement prouver que le contrat est respecté quelle que soit la façon dont la méthode est mise en œuvre. Le fait est qu'il n'y a aucun outil pour vous dire si vous avez entièrement testé le contrat.
Karol Lewandowski
1
@KarolLewandowski Je pense que ce que Shaz dit, c'est que si vous faites confiance au fonctionnement Optional(et aux méthodes connexes), vous n'avez plus à les tester. Pas de la même manière que vous avez testé un if-else: tout ifétait un champ de mines potentiel. Optionalet les idiomes fonctionnels similaires sont déjà codés et garantis pour ne pas vous trébucher, donc essentiellement il y a une "branche" qui a disparu.
Andres F.
1
@AndresF. Je ne pense pas que Karol propose de tester Optional. Comme il l'a dit, logiquement, nous devrions toujours tester qui getName()gère diverses entrées possibles de la manière que nous souhaitons, quelle que soit sa mise en œuvre. Il est plus difficile de déterminer cela sans que les outils de couverture de code ne contribuent à la manière dont ils seraient pré-JDK8.
Mike Partridge
1
@MikePartridge Oui, mais le fait est que cela ne se fait pas via la couverture des succursales. La couverture des succursales est nécessaire lors de l'écriture, if-elsecar chacune de ces constructions est complètement ad hoc. En revanche, Optional, orElse, map, etc, sont tous déjà testés. Les branches, en effet, "disparaissent" lorsque vous utilisez des idiomes plus puissants.
Andres F.