Java Language Qu'est-ce que le test d'unité?


Exemple

C'est un peu une amorce. C'est surtout parce que la documentation est forcée d'avoir un exemple, même si elle est conçue comme un article de remplacement. Si vous connaissez déjà les principes de base des tests unitaires, n'hésitez pas à passer aux remarques où des cadres spécifiques sont mentionnés.

Les tests unitaires garantissent qu'un module donné se comporte comme prévu. Dans les applications à grande échelle, assurer la bonne exécution des modules dans le vide fait partie intégrante de la fidélité des applications.

Considérons le pseudo-exemple suivant (trivial):

public class Example {
  public static void main (String args[]) {
    new Example();
  }

  // Application-level test.
  public Example() {
    Consumer c = new Consumer();
    System.out.println("VALUE = " + c.getVal());
  }

  // Your Module.
  class Consumer {
    private Capitalizer c;
  
    public Consumer() {
      c = new Capitalizer();
    }

    public String getVal() {
      return c.getVal();
    }
  }

  // Another team's module.
  class Capitalizer {
    private DataReader dr;
  
    public Capitalizer() {
      dr = new DataReader();
    }

    public String getVal() {
      return dr.readVal().toUpperCase();
    }
  }

  // Another team's module.
  class DataReader {
    public String readVal() {
      // Refers to a file somewhere in your application deployment, or
      // perhaps retrieved over a deployment-specific network.
      File f; 
      String s = "data";
      // ... Read data from f into s ...
      return s;
    }
  }
}

Donc, cet exemple est trivial; DataReader obtient les données d'un fichier, les transmet au Capitalizer , qui convertit tous les caractères en majuscules, qui sont ensuite transmis au Consumer . Mais le DataReader est fortement lié à notre environnement d’application, donc nous reportons les tests de cette chaîne jusqu’à ce que nous soyons prêts à déployer une version de test.

Maintenant, supposons, quelque part dans une version, pour des raisons inconnues, la méthode getVal() de Capitalizer remplacée par le retour d'une toUpperCase() à une chaîne toLowerCase() :

  // Another team's module.
  class Capitalizer {
    ...

    public String getVal() {
      return dr.readVal().toLowerCase();
    }
  }

Clairement, cela brise le comportement attendu. Mais, en raison des processus ardus impliqués dans l'exécution du DataReader , nous ne le remarquerons pas avant notre prochain déploiement de test. Ainsi, les jours / semaines / mois passent avec ce bogue dans notre système, puis le chef de produit le constate et se tourne instantanément vers vous, le chef d'équipe associé au Consumer . "Pourquoi ça se passe? Qu'est-ce que vous avez changé?" De toute évidence, vous êtes naïf. Vous n'avez aucune idée de ce qui se passe. Vous n'avez modifié aucun code qui devrait toucher à cela; pourquoi est-il brisé soudainement?

Finalement, après discussion entre les équipes et la collaboration, le problème est tracé et le problème résolu. Mais, cela pose la question; Comment cela aurait-il pu être évité?

Il y a deux choses évidentes:

Les tests doivent être automatisés

Notre confiance dans les tests manuels a permis à ce bug de passer inaperçu beaucoup trop longtemps. Nous avons besoin d'un moyen d'automatiser le processus d'introduction instantanée des bogues. Pas 5 semaines à partir de maintenant. Pas 5 jours à partir de maintenant. Pas 5 minutes à partir de maintenant. Maintenant.

Vous devez comprendre que, dans cet exemple, j'ai présenté un bug très trivial qui a été présenté et qui est passé inaperçu. Dans une application industrielle, avec des dizaines de modules constamment mis à jour, ceux-ci peuvent s'introduire partout. Vous corrigez quelque chose avec un module, seulement pour vous rendre compte que le comportement même que vous "corrigiez" était utilisé d'une manière ou d'une autre (en interne ou en externe).

Sans validation rigoureuse, les choses vont se glisser dans le système. Il est possible que, si on les néglige assez, cela se traduira par beaucoup de travail supplémentaire en essayant de corriger les changements (et la fixation de ces corrections, etc.), qu'un produit augmentera effectivement dans le travail restant que l' effort est mis. Vous ne voulez pas être dans cette situation.

Les tests doivent être précis

Le deuxième problème noté dans notre exemple ci-dessus est le temps nécessaire pour tracer le bogue. Le responsable de produit vous a envoyé une requête lorsque les testeurs l'ont remarqué, vous avez enquêté et constaté que le Capitalizer renvoyait des données apparemment mauvaises, vous avez envoyé un message à l'équipe Capitalizer avec vos conclusions, etc.

Le même point que j'ai fait ci-dessus à propos de la quantité et de la difficulté de cet exemple trivial existe ici. De toute évidence, toute personne raisonnablement bien familiarisée avec Java pourrait trouver le problème introduit rapidement. Mais il est souvent beaucoup plus difficile de retracer et de communiquer les problèmes. Peut-être que l'équipe Capitalizer vous a fourni un JAR sans source. Peut-être sont-ils situés à l'autre bout du monde, et les heures de communication sont très limitées (peut-être pour les e-mails envoyés une fois par jour). Cela peut entraîner des bogues prenant des semaines ou plus à tracer (et, encore une fois, il pourrait y en avoir plusieurs pour une version donnée).

Afin d'atténuer ce problème, nous souhaitons des tests rigoureux à un niveau aussi fin que possible (vous souhaitez également des tests grossiers pour vous assurer que les modules interagissent correctement, mais ce n'est pas notre objectif principal). Nous souhaitons spécifier de manière rigoureuse le fonctionnement de toutes les fonctionnalités tournées vers l'extérieur (au minimum) et tester cette fonctionnalité.

Entrer les tests unitaires

Imaginez si nous avions un test, en nous assurant spécifiquement que la méthode getVal() de Capitalizer getVal() une chaîne en majuscule pour une chaîne d'entrée donnée. De plus, imaginez que ce test ait été exécuté avant même que nous ayons commis un code. Le bogue introduit dans le système (qui est, toUpperCase() étant remplacé par toLowerCase() ) causerait aucun problème parce que le bug ne serait jamais introduit dans le système. Nous le prendrions dans un test, le développeur réaliserait (espérons-le) son erreur, et une solution alternative serait trouvée quant à la manière d'introduire l'effet souhaité.

Il y a des omissions ici concernant la manière de mettre en œuvre ces tests, mais celles-ci sont couvertes dans la documentation spécifique au framework (liée dans les remarques). J'espère que cela sert d'exemple pour expliquer pourquoi les tests unitaires sont importants.