Comment TDD une implémentation de liste en Java

1. Vue d'ensemble

Dans ce didacticiel, nous allons parcourir une implémentation de liste personnalisée à l'aide du processus de développement piloté par les tests (TDD).

Ce n'est pas une introduction à TDD, donc nous supposons que vous avez déjà une idée de base de ce que cela signifie et de l'intérêt soutenu pour vous améliorer.

En termes simples, TDD est un outil de conception, nous permettant de piloter notre implémentation à l'aide de tests .

Un avertissement rapide - nous ne nous concentrons pas sur la création d'une mise en œuvre efficace ici - juste en l'utilisant comme excuse pour afficher les pratiques TDD.

2. Premiers pas

Tout d'abord, définissons le squelette de notre classe:

public class CustomList implements List { private Object[] internal = {}; // empty implementation methods } 

La classe CustomList implémente l' interface List , elle doit donc contenir des implémentations pour toutes les méthodes déclarées dans cette interface.

Pour commencer, nous pouvons simplement fournir des corps vides pour ces méthodes. Si une méthode a un type de retour, nous pouvons retourner une valeur arbitraire de ce type, telle que null pour Object ou false pour boolean .

Par souci de concision, nous allons omettre les méthodes optionnelles, ainsi que certaines méthodes obligatoires qui ne sont pas souvent utilisées.

3. Cycles TDD

Développer notre implémentation avec TDD signifie que nous devons d'abord créer des cas de test , définissant ainsi les exigences de notre implémentation. Ce n'est qu'alors que nous créerons ou corrigerons le code d'implémentation pour que ces tests réussissent.

De manière très simplifiée, les trois étapes principales de chaque cycle sont:

  1. Rédaction de tests - définir les exigences sous forme de tests
  2. Implémentation de fonctionnalités - faites passer les tests sans trop vous concentrer sur l'élégance du code
  3. Refactoring - améliorez le code pour le rendre plus facile à lire et à maintenir tout en réussissant les tests

Nous allons parcourir ces cycles TDD pour certaines méthodes de l' interface List , en commençant par les plus simples.

4. La méthode isEmpty

La méthode isEmpty est probablement la méthode la plus simple définie dans l' interface List . Voici notre implémentation de départ:

@Override public boolean isEmpty() { return false; }

Cette définition de méthode initiale suffit à compiler. Le corps de cette méthode sera «forcé» de s'améliorer lorsque de plus en plus de tests seront ajoutés.

4.1. Le premier cycle

Écrivons le premier cas de test qui garantit que la méthode isEmpty renvoie true lorsque la liste ne contient aucun élément:

@Test public void givenEmptyList_whenIsEmpty_thenTrueIsReturned() { List list = new CustomList(); assertTrue(list.isEmpty()); }

Le test donné échoue car la méthode isEmpty renvoie toujours false . Nous pouvons le faire passer simplement en retournant la valeur de retour:

@Override public boolean isEmpty() { return true; }

4.2. Le deuxième cycle

Pour confirmer que la méthode isEmpty renvoie false lorsque la liste n'est pas vide, nous devons ajouter au moins un élément:

@Test public void givenNonEmptyList_whenIsEmpty_thenFalseIsReturned() { List list = new CustomList(); list.add(null); assertFalse(list.isEmpty()); }

Une implémentation de la méthode add est désormais requise. Voici la méthode add avec laquelle nous commençons:

@Override public boolean add(E element) { return false; }

Cette implémentation de méthode ne fonctionne pas car aucune modification de la structure de données interne de la liste n'est effectuée. Mettons-le à jour pour stocker l'élément ajouté:

@Override public boolean add(E element) { internal = new Object[] { element }; return false; }

Notre test échoue toujours car la méthode isEmpty n'a pas été améliorée. Faisons cela:

@Override public boolean isEmpty() { if (internal.length != 0) { return false; } else { return true; } }

Le test non vide réussit à ce stade.

4.3. Refactoring

Les deux cas de test que nous avons vus jusqu'à présent passent, mais le code de la méthode isEmpty pourrait être plus élégant.

Refactorisons-le:

@Override public boolean isEmpty() { return internal.length == 0; }

Nous pouvons voir que les tests réussissent, donc l'implémentation de la méthode isEmpty est maintenant terminée.

5. La méthode de taille

Voici notre implémentation de départ de la méthode size permettant à la classe CustomList de se compiler:

@Override public int size() { return 0; }

5.1. Le premier cycle

En utilisant la méthode add existante , nous pouvons créer le premier test pour la méthode size , en vérifiant que la taille d'une liste avec un seul élément est 1 :

@Test public void givenListWithAnElement_whenSize_thenOneIsReturned() { List list = new CustomList(); list.add(null); assertEquals(1, list.size()); }

Le test échoue car la méthode de taille renvoie 0 . Faisons-le passer avec une nouvelle implémentation:

@Override public int size() { if (isEmpty()) { return 0; } else { return internal.length; } }

5.2. Refactoring

Nous pouvons refactoriser la méthode de taille pour la rendre plus élégante:

@Override public int size() { return internal.length; }

L'implémentation de cette méthode est maintenant terminée.

6. La méthode get

Voici l'implémentation de départ de get :

@Override public E get(int index) { return null; }

6.1. Le premier cycle

Jetons un coup d'œil au premier test de cette méthode, qui vérifie la valeur de l'élément unique dans la liste:

@Test public void givenListWithAnElement_whenGet_thenThatElementIsReturned() { List list = new CustomList(); list.add("baeldung"); Object element = list.get(0); assertEquals("baeldung", element); }

Le test passera avec cette implémentation de la méthode get :

@Override public E get(int index) { return (E) internal[0]; }

6.2. Amélioration

Habituellement, nous ajoutons plus de tests avant d'apporter des améliorations supplémentaires à la méthode get . Ces tests auraient besoin d'autres méthodes de l' interface List pour implémenter les assertions appropriées.

However, these other methods aren't mature enough, yet, so we break the TDD cycle and create a complete implementation of the get method, which is, in fact, not very hard.

It's easy to imagine that get must extract an element from the internal array at the specified location using the index parameter:

@Override public E get(int index) { return (E) internal[index]; }

7. The add Method

This is the add method we created in section 4:

@Override public boolean add(E element) { internal = new Object[] { element }; return false; }

7.1. The First Cycle

The following is a simple test that verifies the return value of add:

@Test public void givenEmptyList_whenElementIsAdded_thenGetReturnsThatElement() { List list = new CustomList(); boolean succeeded = list.add(null); assertTrue(succeeded); }

We must modify the add method to return true for the test to pass:

@Override public boolean add(E element) { internal = new Object[] { element }; return true; }

Although the test passes, the add method doesn't cover all cases yet. If we add a second element to the list, the existing element will be lost.

7.2. The Second Cycle

Here's another test adding the requirement that the list can contain more than one element:

@Test public void givenListWithAnElement_whenAnotherIsAdded_thenGetReturnsBoth() { List list = new CustomList(); list.add("baeldung"); list.add(".com"); Object element1 = list.get(0); Object element2 = list.get(1); assertEquals("baeldung", element1); assertEquals(".com", element2); }

The test will fail since the add method in its current form doesn't allow more than one element to be added.

Let's change the implementation code:

@Override public boolean add(E element) { Object[] temp = Arrays.copyOf(internal, internal.length + 1); temp[internal.length] = element; internal = temp; return true; }

The implementation is elegant enough, hence we don't need to refactor it.

8. Conclusion

Ce didacticiel a suivi un processus de développement piloté par les tests pour créer une partie d'une implémentation de liste personnalisée . Grâce à TDD, nous pouvons implémenter les exigences étape par étape, tout en maintenant la couverture des tests à un niveau très élevé. En outre, l'implémentation est garantie d'être testable, car elle a été créée pour faire passer les tests.

Notez que la classe personnalisée créée dans cet article est uniquement utilisée à des fins de démonstration et ne doit pas être adoptée dans un projet réel.

Le code source complet de ce didacticiel, y compris les méthodes de test et d'implémentation laissées de côté par souci de brièveté, est disponible sur GitHub.