IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage Java Discussion :

Tests unitaires et découpage de classe


Sujet :

Langage Java

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre confirmé
    Homme Profil pro
    Développeur Web
    Inscrit en
    Décembre 2006
    Messages
    126
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Décembre 2006
    Messages : 126
    Par défaut Tests unitaires et découpage de classe
    Bonjour,

    Cela fait 5 ans que je bosse en Java et seulement 1an que j'ai découvert le réel intérêt de tests unitaires (comme quoi il n'est jamais trop tard).
    Je tâtonne encore donc avec les bonnes pratiques et je tombe de façon récurrente sur un problème de design.

    Prenons l'exemple d'un morceau de programme qui converti des objets d'un framework dans un autre.

    Modèle librairie 1
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    public class Panier {
        private Collection<Fruit> fruits;
        // getters, setters, equals, hashCode, ...
        ...
    }
    public class Fruit {
        private String nom;
        // getters, setters, equals, hashCode, ...
        ...
    }
    Modèle librairie 2
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    public class Basket {
        private Map<Integer, JuicyFruit> juicyFruits;
        // getters, setters, equals, hashCode, ...
    }
    public class JuicyFruit {
        private String name;
        // getters, setters, equals, hashCode, ...
    }
    Pour convertir des objets d'une librairie dans l'autre, j'étais tenté d'écrire une seule classe qui faisait le boulot :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     
    public class ObjectsConverter {
        public Basket convert(Panier panier) {
            Basket basket = ...
            // logique plus ou moins complexe
            for (Fruit fruit : panier.getFruits()) {
                basket.add(this.convert(fruit));
            }
            return basket;
        }
     
        public JuicyFruit convert(Fruit fruit) {
            JuicyFruit jFruit = ...
            // logique plus ou moins complexe
            return jFruit;        
        }
    }
    Le problème avec la classe ci dessus c'est que l'écriture des tests unitaires peut très vite devenir fastidieuse à cause de l'appel en cascade des méthode "convert". Convertir un "Panier" oblige à passer des objet "Fruits" valide sous peine de générer des erreurs indésirables et la construction de tous ces objets peut vite rendre le test illisible / pas maintenable.

    La solution que j'ai trouvée jusqu'à présent est de créer 2 classes séparées qui convertissent chacune un des objet (1 convertisseur de "Panier" et un convertisseur de "Fruit")
    En utilisant l'inversion de contrôle, des interfaces et des mocks, les tests unitaires redeviennent simples.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
     
    public class PanierConverter {
        private FruitConverter fruitConverter;
     
        public Basket convert(Panier panier) {
            Basket basket = ...
            // logique plus ou moins complexe
            for (Fruit fruit : panier.getFruits()) {
                basket.add(this.fruitConverter.convert(fruit));
            }
            return basket;
        }
    }
    public class FruitConverter {
        public JuicyFruit convert(Fruit fruit) {
            JuicyFruit jFruit = ...
            // logique plus ou moins complexe
            return jFruit;        
        }
    }
    Seulement cette technique m'embête car elle est prompte à créer des dizaines de classes et interfaces ne contenant qu'une seule pauvre méthode qu'on peine à nommer de façon appropriée. Et du coup c'est l'API qu'on crée qui devient touffue et difficile à utiliser.
    On peut évidemment utiliser un pattern façade pour re-simplifier l'API mais ça ressemble au tapis sous lequel on cache la poussière.

    Je me demandais donc comment d'autres professionnels adressent cette problématique.

    D'avance merci pour vos réponses.

  2. #2
    Membre Expert
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Par défaut
    As-tu la possibilité de déléguer la transformation de Fruit en JuicyFruit directement dans la classe Fruit?
    Tu limiterais ainsi les tests unitaires à chaque nouveau couple de Fruit/JuicyFruit.

    Pour ce qui est du côté fastidieux, long et touffu des tests unitaires... Ben ceux que je connais le sont un peu tous vu qu'on essaye systématiquement de tester de manière exhaustive les comportements des composants testés à partir de jeux de données fixes.
    Après, les règles du polymorphisme s'appliquent aussi aux tests unitaires, donc tu peux faire des tests génériques puis des implémentations de tes tests qui dépendent des classes que tu veux tester.

  3. #3
    Membre confirmé
    Homme Profil pro
    Développeur Web
    Inscrit en
    Décembre 2006
    Messages
    126
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Décembre 2006
    Messages : 126
    Par défaut
    Merci eulbobo pour ta réponse,

    As-tu la possibilité de déléguer la transformation de Fruit en JuicyFruit directement dans la classe Fruit?
    Non pour 2 raisons
    1. Ca crée une interdépendances entre les librairies ce qui n'est pas souhaitable
    2. Les méthodes de conversion complexe nécessite parfois l’intervention d'autres services qui ne font pas partie de ma couche modèle.


    Je suis assez d'accord de dire que les tests unitaires doivent exhaustivement tester le comportement cependant l'appel de méthode en cascade a 2 défauts :
    1. On ne test plus de manière isolée une méthode
    2. Les combinaisons pour tester tous les cas des 2 méthodes appelées sont très nombreuse et la quantité de tests nécessaire aussi


    Ma vision est de créer des tests les plus atomiques possibles pour tester isolément et exhaustivement chaque méthode.
    Si chaque méthode peut être testée indépendamment, les tests unitaires sont restreints.
    Ensuite viennent les tests d'intégration qui test les interactions entre plusieurs objets et l'environnement d'intégration permet d'avoir des jeux de données.

    Après, les règles du polymorphisme s'appliquent aussi aux tests unitaires, donc tu peux faire des tests génériques puis des implémentations de tes tests qui dépendent des classes que tu veux tester.
    L'héritage dans les tests unitaires, j'en ai fait très peu jusqu'à présent car je n'ai pas trouvé que de façon efficace de m'en servir.
    Il faudrait que je vois quelques exemple de bon héritage en matière de tests pour me faire une idée de ce que je pourrais faire.

  4. #4
    Membre Expert
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Par défaut
    Bon, en fait, c'est plus simple qu'il n'y parait.
    Ton code de conversion possède deux fonctions.

    Ce que tu dois tester dans l'ordre c'est :
    - la conversion des instances de Fruit vers leurs instances de JuicyFruit (et juste ça)
    - la conversion des instances de Basket vers Panier (sans éléments dans la liste)

    Si ces deux tests passent pour n'importe quel couple Fruit/JuicyFruit et Basket/Panier, on peut globalement dire que tu as couvert presque tous les cas.
    Reste la conversion des éléments de la liste mais... Globalement, si tu sais que la conversion d'un Fruit vers JuicyFruit fonctionne, pourquoi retester que ça fonctionne dans une boucle? Tu veux vraiment tester que ta boucle for fonctionne dans un test unitaire ?
    Ce que tu pourrais tester par rapport à la collections, c'est surtout la cohérence du rendu quel que soit le type de collection utilisé : ArrayList ou HashSet par exemple. Ce qui revient à tester que le hashCode et le equals de tes couples d'objets sont cohérents entre eux... Donc teste la cohérence equals/hashCode de tes couples d'objets !

    Pour tester la cohérence, tu prends 2 objets Fruits différents (A et B), tu génères leur JuicyFruit correspondant, et tu dois avoir toujours le même résultat sur les tests A.equals(B) et jA.equals(jB).


    Teste le travail attendu d'une classe méthode par méthode en partant de la plus basse/simple. Et si c'est bon, les méthodes qui appellent cette première méthode rendront le bon résultat par principe

  5. #5
    Membre confirmé
    Homme Profil pro
    Développeur Web
    Inscrit en
    Décembre 2006
    Messages
    126
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 38
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Décembre 2006
    Messages : 126
    Par défaut
    Globalement ça fonctionne bien oui mais il reste 2 cas qui ne fonctionnent pas :
    1. si par contrat la classe Basket ou Panier n'accepte pas que la collection qu'elle contient soit vite
    2. si dans la boucle il y a plus de traitement qu'un simple appel à l'autre fonction, ce traitement ne peut pas être testé sans passer par l'autre méthode


    "Devil is in the details" évidemment et il y aura toujours des cas particuliers.

    Je suis plus dans une réflexion générale et ton point de vue me permet de réfléchir, merci.

  6. #6
    Membre Expert
    Avatar de eulbobo
    Homme Profil pro
    Développeur Java
    Inscrit en
    Novembre 2003
    Messages
    786
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Développeur Java

    Informations forums :
    Inscription : Novembre 2003
    Messages : 786
    Par défaut
    Citation Envoyé par Ghurdyl Voir le message
    Globalement ça fonctionne bien oui mais il reste 2 cas qui ne fonctionnent pas :
    1. si par contrat la classe Basket ou Panier n'accepte pas que la collection qu'elle contient soit vite
    2. si dans la boucle il y a plus de traitement qu'un simple appel à l'autre fonction, ce traitement ne peut pas être testé sans passer par l'autre méthode
    Y'a rien dans ton code qui te permet de dire ou de t'assurer que la liste doit toujours être remplie. Comme tu le dis, c'est par contrat, donc à moins de rajouter cette contrainte en renvoyant une exception si la liste est vide, tu ne pourras pas tester ce cas précis.

    Si dans la boucle il y a plus de traitements --> refactorisation des traitements dans une autre méthode que tu pourras elle aussi tester individuellement avec les différents cas de figure.
    Diviser pour mieux régner !
    Essayer au maximum de séparer les traitements unitaires (suites d'opérations qui représentent un tout) des traitements généraux (boucle sur une liste d'éléments pour les traiter individuellement tous de la même façon).

Discussions similaires

  1. Réponses: 0
    Dernier message: 21/03/2015, 13h56
  2. Test unitaire de classe à composante xaml
    Par postb99 dans le forum Windows Presentation Foundation
    Réponses: 2
    Dernier message: 19/07/2013, 16h56
  3. [Framework] Autowired ne fonctionne que dans ma classe de tests unitaires
    Par nicknolt dans le forum Spring
    Réponses: 4
    Dernier message: 29/01/2013, 10h18
  4. [ZF 1.8] [Test Unitaire] Classe Zend_Db_Table not found
    Par titou_777 dans le forum Zend Framework
    Réponses: 6
    Dernier message: 28/07/2009, 11h34
  5. Réponses: 4
    Dernier message: 03/07/2009, 19h06

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo