Spécifications, Tests et écriture de code

Ce document couvre les notions fondamentales liées aux spécifications fonctionnelles, à la conception, aux tests unitaires et à l’écriture de code en génie logiciel.

D'après le document Spécifications, Tests et écriture de code

Cet article a été rédigé automatiquement à partir du document source, puis vérifié avant publication.

Spécifications, Tests et écriture de code

Document source

Spécifications, Tests et écriture de code

Programming, Software Engineering · PDF · 16 pages · 1969

Afficher l'aperçu du document

Consulter le document original →

Ce document couvre les notions fondamentales liées aux spécifications fonctionnelles, à la conception, aux tests unitaires et à l’écriture de code en génie logiciel. Il s’adresse principalement aux étudiants en informatique et aux développeurs souhaitant approfondir leur compréhension du processus de développement logiciel, notamment à travers la programmation pilotée par les tests (TDD) et un exemple concret d’implémentation en Java.

Le processus de conception d’un programme

La conception d’un programme suit plusieurs étapes clés :

  • Précondition : Comprendre les exigences fonctionnelles (ce que le programme doit faire) et non fonctionnelles (critères de qualité) est indispensable pour définir les préconditions et postconditions des méthodes.
  • Conception (Design) : Définir l’architecture logique, le modèle de comportement, les interfaces, et choisir les structures de données adaptées.
  • Écriture du code : Implémenter les fonctionnalités conformément aux spécifications.
  • Tests : Vérifier que le code fonctionne correctement et répond aux exigences.

Les principes de conception tels que le masquage de l’information, l’utilisation des interfaces Java et des classes abstraites jouent un rôle important dans la qualité et la maintenabilité du code.

Les spécifications fonctionnelles

Les spécifications fonctionnelles décrivent précisément le comportement attendu d’un logiciel. Elles doivent :

  • Inclure les comportements normaux et anormaux attendus.
  • Préciser les critères de qualité retenus.
  • Être complètes, non ambiguës, consistantes, non verbeuses, testables et correctes.

Par exemple, la méthode distance(Node noeud1, Node noeud2) d’une classe Graph doit retourner la distance entre deux nœuds ou -1 si ces nœuds ne sont pas reliés. Cette règle doit être clairement documentée, souvent via la javadoc.

Le cahier des charges est un document essentiel qui fixe les besoins fonctionnels avant le développement, afin d’éviter les dérives.

Les tests unitaires et la programmation conduite par les tests (TDD)

Les tests unitaires permettent de vérifier que chaque morceau de code fonctionne comme prévu. Ils améliorent la qualité générale du logiciel et clarifient la documentation.

La méthode TDD recommande d’écrire d’abord les tests unitaires, puis le code qui les fait passer, selon le cycle suivant :

  1. Écrire un test basé sur les spécifications fonctionnelles.
  2. Vérifier que le test échoue (car le code n’existe pas encore).
  3. Écrire le code pour faire passer le test.
  4. Vérifier que le test passe.
  5. Refactoriser le code en s’assurant que le test reste valide.

Cette démarche permet de réfléchir aux différents cas d’usage avant même d’avoir implémenté la fonctionnalité.

Que faut-il tester ?

Il est important de couvrir :

  • Les cas représentatifs et les cas d’erreurs.
  • Les conditions limites.
  • Les situations complexes et les stress tests.
  • Les choix d’implémentation, notamment les structures de données et la coopération entre objets.

Bien que cela nécessite d’écrire plus de code, les tests unitaires facilitent la maintenance et la collaboration en équipe.

Exemple d’analyse de tests pour une méthode

Considérons la méthode suivante :

/**
 * Met la valeur 0 sur les len premières valeurs du tableau.
 *
 * @param tab, le tableau de départ d’une longueur d’au moins len
 * @param len, le nombre de cases à mettre à 0
 */
public void resetPartiel(int[] tab, int len)

Les tests à envisager sont :

  • Test avec un tableau vide.
  • Test avec un tableau non alloué (null).
  • Test avec un tableau de taille 1 puis 2.
  • Test avec une valeur de len invalide (négative, nulle, supérieure à la taille du tableau).
  • Test avec un tableau très grand pour évaluer les performances.

Black Box Testing et White Box Testing

Le Black Box Testing consiste à tester les fonctionnalités sans connaissance de l’implémentation, en se concentrant sur les cas difficiles et les comportements attendus.

Le White Box Testing consiste à tester en connaissant le code source, en vérifiant les structures de données, les itérations et la coopération entre objets.

Le framework JUnit

JUnit est un environnement de test unitaire très utilisé en industrie, intégré dans la plupart des IDE comme Eclipse ou IntelliJ. Il permet d’organiser les tests dans un répertoire dédié et propose des annotations pour gérer le cycle de vie des tests :

  • @Test : indique une méthode de test.
  • @BeforeEach : méthode exécutée avant chaque test, pour initialiser l’objet testé.
  • @AfterEach : méthode exécutée après chaque test, pour nettoyer (fermer fichiers, connexions...).
  • @BeforeAll : méthode exécutée une fois avant tous les tests.

JUnit fournit aussi des assertions pour vérifier les résultats :

  • assertEquals(expected, actual)
  • assertTrue(condition), assertFalse(condition)
  • assertNotNull(object), assertNull(object)
  • assertSame(expected, actual), assertNotSame(expected, actual)

Exemple complet : Prime Factors Kata

Le Prime Factors Kata est un exercice de programmation visant à écrire une méthode statique generate dans une classe PrimeFactors qui, donnée un entier n, retourne la liste des facteurs premiers de n dans l’ordre croissant.

Définition de la classe et méthode initiale

package primeFactors;

import java.util.*;

public class PrimeFactors {

  public static List<Integer> generate(int n) {
    return new ArrayList<Integer>();
  }

}

Définition des tests unitaires

package primeFactors;

import junit.framework.TestCase;
import java.util.*;

public class PrimeFactorsTest extends TestCase {

  private List<Integer> list(int... ints) {
    List<Integer> list = new ArrayList<Integer>();
    for (int i : ints)
      list.add(i);
    return list;
  }

  public void testOne() throws Exception {
    assertEquals(list(), PrimeFactors.generate(1));
  }

  public void testTwo() throws Exception {
    assertEquals(list(2), PrimeFactors.generate(2));
  }

  public void testThree() throws Exception {
    assertEquals(list(3), PrimeFactors.generate(3));
  }

  public void testFour() throws Exception {
    assertEquals(list(2, 2), PrimeFactors.generate(4));
  }

  public void testSix() throws Exception {
    assertEquals(list(2, 3), PrimeFactors.generate(6));
  }

  public void testEight() throws Exception {
    assertEquals(list(2, 2, 2), PrimeFactors.generate(8));
  }

  public void testNine() throws Exception {
    assertEquals(list(3, 3), PrimeFactors.generate(9));
  }
}

Évolution progressive de la méthode generate

La méthode est améliorée au fur et à mesure des tests :

public static List<Integer> generate(int n) {
  List<Integer> primes = new ArrayList<Integer>();
  for (int candidate = 2; n > 1; candidate++) {
    for (; n % candidate == 0; n /= candidate)
      primes.add(candidate);
  }
  return primes;
}

Cette version utilise deux boucles imbriquées :

  • La boucle externe incrémente le candidat facteur premier.
  • La boucle interne divise n autant de fois que possible par ce candidat et ajoute ce facteur à la liste.

Exemple d’exécution

Pour n = 12 :

  • candidate = 2 : 12 % 2 == 0, ajoute 2, n devient 6
  • 6 % 2 == 0, ajoute 2, n devient 3
  • candidate = 3 : 3 % 3 == 0, ajoute 3, n devient 1
  • Fin, retourne [2, 2, 3]

Glossaire des termes clés

  • Précondition : Condition devant être vraie avant l’exécution d’une méthode.
  • Postcondition : Condition devant être vraie après l’exécution d’une méthode.
  • Spécifications fonctionnelles : Description précise des comportements attendus d’un logiciel.
  • Test unitaire : Test vérifiant le comportement d’une unité de code isolée.
  • Test Driven Development (TDD) : Méthode de développement où les tests sont écrits avant le code.
  • Black Box Testing : Test basé sur les spécifications sans connaissance du code.
  • White Box Testing : Test basé sur la connaissance du code source.
  • JUnit : Framework Java pour l’écriture et l’exécution des tests unitaires.
  • Assertion : Instruction vérifiant qu’une condition est vraie dans un test.
  • Refactorisation : Modification du code pour l’améliorer sans changer son comportement.

Points clés à retenir

  • La compréhension des exigences fonctionnelles et non fonctionnelles est essentielle avant de coder.
  • Les spécifications fonctionnelles doivent être complètes, non ambiguës, consistantes et testables.
  • Les tests unitaires garantissent la conformité du code aux spécifications et facilitent la maintenance.
  • La programmation conduite par les tests (TDD) améliore la qualité du code en écrivant d’abord les tests.
  • JUnit est un outil puissant et largement utilisé pour automatiser les tests unitaires en Java.
  • L’exemple du Prime Factors Kata illustre l’évolution progressive d’une méthode via des tests successifs.

Partager

Commentaires

Aucun commentaire pour le moment. Posez la première question.

Les commentaires sont relus avant publication. Votre e-mail n'est jamais affiché.

← Toutes les révisions