Site icon Blog Zenika

Faire des tests unitaires est impossible.

A la question “qu’est ce qu’un test unitaire” la réponse  la plus fréquemment donnée est “c’est un test qui teste une fonction en isolation du reste du code”. Or cette définition est fausse ! Afin de le démontrer, prenons un cas simple : nous voulons tester la fonction add de la classe BigDecimal.
Nous écrivons alors le test suivant :

    @Test
    public void shouldAddTwoBigDecimals(){
       Assertions.assertThat(new BigDecimal("3").add(new BigDecimal("2")))
                 .isEqualTo(new BigDecimal("5"));
    }

Au premier regard, tout va bien, nous ne testons que la fonction add. Mais en y regardant de plus près, nous testons aussi le constructeur! Certes «le constructeur est trivial et ne fait rien de compliqué», n’est-ce pas ? Vu de l’extérieur, oui, mais si nous regardons le code, le constructeur fait beaucoup de choses !
A ce stade de la démonstration, il est fréquent d’entendre «oui mais les constructeurs ne sont pas vraiment des fonctions» ou encore «le constructeur est une fonction particulière, elle fait exception à la règle des tests unitaires». Pour l’exercice, admettons.
Regardons de plus près cette assertion : elle utilise la fonction equals ! Cette fois ci, pas d’exception qui tienne ! Equals est une fonction comme une autre (beaucoup plus difficile à écrire correctement qu’il n’y paraît d’ailleurs) !
Au final, si le test est rouge, cela peut venir de trois endroits différents :

Nous sommes dans un cas très simple, loin des complexités réelles de vrais projets, et pourtant, nous avons déjà cassé notre définition.
Tout ceci n’aurait rien de grave si nous nous arrêtions là. Cependant, cette définition nuit gravement au code. Beaucoup de tests sont écrits aujourd’hui dans l’espoir de réussir à respecter cette définition. Du coup, on s’autorise des entorses «acceptables» :

Les deux premiers points sont faux mais pas dramatiques. Le vrai problème réside dans le troisième point car, sous prétexte de vouloir faire des tests unitaires (ce qui est très bien !), nous dégradons (fortement) le design de notre code (ce qui est très mal !).
Imaginons cela sur notre exemple : nous pouvons alors vérifier que la fonction add est correcte en vérifiant l’état de l’objet grâce aux getters.
Sauf qu’à faire des getters et des setters partout :

Ces deux points ont deux conséquences :

Si vous regardez d’un peu plus près l’implémentation de BigDecimal, vous comprendrez très vite pourquoi vous ne voulez pas que votre code de test (et encore plus votre code de production) soit conscient des détails d’implémentation de l’objet !
Beaucoup de définitions différentes de «test unitaire» existent et le but de cet article n’est pas de donner une «bonne» définition, mais bien d’alerter sur les dangers de la définition la plus commune.
En espérant vous avoir convaincu d’utiliser une autre définition !
P.S: la définition actuellement utilisée par l’auteur est : «Un test est unitaire si le code sous test ne dépend que du langage (pas de framework, pas de base de donnée,…)»

Auteur/Autrice

Quitter la version mobile