Deux Exemples Fréquents de Couplage Tests/Implémentation
Tags: tdd, testing, java, french
Published: 2022-09-22 10:05:28

Supposons que vous souhaitez tester la classe **User** suivante (très simpliste pour l'exemple):

```java
public class User {
    private String firstname;
    private String lastname;

    public User(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }

    public String displayName() {
        return firstname + " " + lastname;
    }
}
```

Voici comment certains développeurs feraient:

```java
@Test
void displayName() {
    var firstname = "John";
    var lastname = "Doe";
    var user = new User(firstname, lastname);

    assertEquals(firstname + " " + lastname, user.displayName(), "The display name should contain the firstname and the lastname.");
}
```

plutôt que:

```java
@Test
void displayName() {
    var user = new User("John", "Doe");

    assertEquals("John Doe", user.displayName(), "The display name should contain the firstname and the lastname.");
}
```

La logique de leur raisonnement est de vouloir supprimer la duplication des constantes représentant le nom et le prénom dans le test. Parce que la duplication, c'est forcément BAD.

Au-delà du fait que le second test est plus court et plus simple à lire, éliminer cette duplication en introduit une autre qui est bien plus dangereuse.

Sauriez-vous dire laquelle ?

Il s'agit du code `firstname + " " + lastname` présent dans le premier test. Et ce que l'on a dupliqué ici est l'algorithme utilisé par la classe **User** pour générer le nom à afficher.

En faisant cela, on introduit **un couplage entre notre test et son implémentation**. Et c'est une chose à éviter.

Pour quelle raison ? Parce plus un test est indépendant de l'implémentation du code testé, meilleur il est: imaginez que l'algorithme change côté production, voilà qu'on doive également le transposer dans les tests.

Traitez le code de production comme une boîte noire pour éviter de construire des tests fragiles.

Bien sûr, ce n'est parfois pas possible (j'en parlerais peut-être une autre fois) mais on doit s'évertuer à suivre cette règle: cela rendra nos tests beaucoup **moins fragiles**.

En résumé, ne copiez pas l'algorithme à tester dans vos tests !

Il y a une autre forme de duplication plus subtile mais que j'ai souvent vue même chez des développeurs expérimentés. Considérez le test d'intégration suivant:

```java
@Test
void findUser() {
    var expectedUser = new User(1, "John", "Doe");
    var response = restTemplate.getForEntity("/users/1", User.class);
    JSONAssert.assertEquals(expected, response.getBody(), false);
}
```

J'ai éliminé toute la tuyauterie pour nous concentrer sur la partie utile du test: on utilise Spring Boot mais ça pourrait être autre chose.

Quel est le problème ici ?

Eh bien, on utilise la classe **User** dans notre test d'intégration et cette classe vient de notre code de production.

Pareil que pour précédemment, on a **couplé** notre test avec le code de production.

La justification est souvent de dire qu'il est plus simple de réutiliser la classe **User** plutôt que d'écrire notre test indépendamment de celle-ci.

Supposez que l'on renomme des propriétés dans la classe **User** dans le cadre d'un refactoring: ce test passera toujours et ne le détectera pas.

Cela veut dire que notre API ne respectera plus son contrat et ce sont les clients de cette API qui vont être mécontents.

Pour les tests d'intégration de ce type, n'utilisez pas de code côté production. Voici un meilleur test dans ce contexte:

```java
@Test
void findUser() {
    String expectedUser = "{id:1, firstname:\"John\", lastname:\"Doe\"}";
    var response = restTemplate.getForEntity("/users/1", String.class);
    JSONAssert.assertEquals(expected, response.getBody(), false);
}
```

La solution est simplement de considérer la sortie JSON comme une **String** plutôt qu'un **User**.

Certes, cela n'a pas l'air joli mais pareil qu'avant, votre test sera beaucoup **plus robuste** en capturant les modifications qui changent le contrat de vos APIs.