OVH Cloud OVH Cloud

JUnit, un petit exemple ?

7 réponses
Avatar
Zouplaz
Bonjour, je voudrais bien essayer de me mettre à l'extreme programming.
J'ai lu un peu de littérature sur le sujet, mais j'ai du mal à voir la mise
en oeuvre concrète de l'utilisation intensives des tests.

Voici le genre d'exemple qui me pose problème : Imaginons une classe
Serveur - Imaginons encore que cette classe ouvre un socket, le socket
étant une variable privée.
Et enfin, imaginons que je veuille écrire un test qui vérifie la bonne
connection au serveur distant.

Pour savoir si la connexion a réussi ou pas :

- serveur.login(...)
- ici j'aurais un test du genre assertNotNull(serveur.socket)

Mais comme socket est une variable privée, je dois trouver une solution
pour y accéder ou alors ne pas réaliser de test dessus (cf.
http://www.artima.com/suiterunner/private.html)

Concrètement, dans un cas comme celui là j'ai beaucoup de mal à envisager
une solution dans laquelle je ne testerais pas la valeur de socket.
Je n'envisage pas de polluer la classe serveur avec une méthode public
isConnected(), celle ci n'ayant de raison d'exister que dans le cadre du
test, et je ne vois pas comment une série de tests peut être efficace si
elle ne porte que sur les méthodes et données publiques.

Si la classe serveur ne définie que 4 méthodes publiques :
serveur.login(...)
serveur.send(...)
serveur.receive(...)
serveur.loggout(...)

il me semble que les tests devront nécessairement porter sur les mécanismes
internes de la classe (chacune de ces 4 methodes faisant appel à de
nombreuses méthodes privées, imaginons les toutes plus complexes les unes
que les autres), sinon quel intérêt ?

Voila, pour reprendre mon exemple avec la variable privée socket j'aimerais
bien que quelqu'un me donne son point de vue et m'explique ce qui cloche
dans mon approche. Comment tester la bonne connexion au serveur distant ?

Merci d'avance

7 réponses

Avatar
Benoit EONNET
Zouplaz wrote:
Bonjour, je voudrais bien essayer de me mettre à l'extreme programming.
J'ai lu un peu de littérature sur le sujet, mais j'ai du mal à voir la mise
en oeuvre concrète de l'utilisation intensives des tests.

Voici le genre d'exemple qui me pose problème : Imaginons une classe
Serveur - Imaginons encore que cette classe ouvre un socket, le socket
étant une variable privée.
Et enfin, imaginons que je veuille écrire un test qui vérifie la bonne
connection au serveur distant.

Pour savoir si la connexion a réussi ou pas :

- serveur.login(...)
- ici j'aurais un test du genre assertNotNull(serveur.socket)

Mais comme socket est une variable privée, je dois trouver une solution
pour y accéder ou alors ne pas réaliser de test dessus (cf.
http://www.artima.com/suiterunner/private.html)

Concrètement, dans un cas comme celui là j'ai beaucoup de mal à envisager
une solution dans laquelle je ne testerais pas la valeur de socket.
Je n'envisage pas de polluer la classe serveur avec une méthode public
isConnected(), celle ci n'ayant de raison d'exister que dans le cadre du
test, et je ne vois pas comment une série de tests peut être efficace si
elle ne porte que sur les méthodes et données publiques.

Si la classe serveur ne définie que 4 méthodes publiques :
serveur.login(...)
serveur.send(...)
serveur.receive(...)
serveur.loggout(...)

il me semble que les tests devront nécessairement porter sur les mécanismes
internes de la classe (chacune de ces 4 methodes faisant appel à de
nombreuses méthodes privées, imaginons les toutes plus complexes les unes
que les autres), sinon quel intérêt ?

Voila, pour reprendre mon exemple avec la variable privée socket j'aimerais
bien que quelqu'un me donne son point de vue et m'explique ce qui cloche
dans mon approche. Comment tester la bonne connexion au serveur distant ?

Merci d'avance




JUnit ne peut effectuer des tests que sur des méthodes public, protected
et de visibilité default si ta classe de test est dans le même package
que la classe à tester. Donc les méthodes privates ne pourront être
DIRECTEMENT testées.

Malgrès tout, les tests unitaires te permettront de tester indirectement
tes méthodes privées par le biais des méthodes public/protected/default
que tu testeras avec JUnit.

Il est évident que si une méthode private ne peut être testée par ce
biais, ceci signifie qu'elle n'est d'aucune utilité pour ta classe.

Maintenant d'un point de vue "eXtrem Programming", il convient de créer
d'abord le squelette de ta classe avec les méthodes qui doivent
retournées des valeurs par défaut. Lorsque ce squelette est prêt tu
écris l'ensemble de tes tests unitaires qui devront mettre en oeuvre
chaque méthode (hormis les private bien entendu). Tu testes le test
unitaire pour contrôler que tu n'as pas de bugs dû à un test mal écrit.
Puisque ta classe à tester ne contient que des méthodes "vides" il sera
alors plus facile de mettre le test au point. Ensuite tu complètes les
méthodes de la classe à tester. A chaque fois qu'une méthode est écrite,
tu peux rejouer tes tests unitaires pour vérifier que tu n'as pas de
régression lors de ton développement.

Bien entendu, avec le temps et la pratique, tu auras d'autres idées sur
la façon de tester ta classe. Lors du développement il n'est pas rare de
s'apercevoir d'un comportement ou d'un problème que l'on ne soupsonnait
pas a priori. Ceci est tout à fait normal, puisque tu acquières de
l'expérience sur le problème à traiter en développant les méthodes de ta
classe. Dans ce cas il te faudra compléter ou ajouter d'autres tests
unitaires dans ta classe de tests pour t'assurer de couvrir le maximum
de cas de figure.

Enfin, lorsque tu auras développé un certains nombres de classes "JUnit"
de tests unitaires, tu pourras les intégrer dans une "Test Suite" qui
est une classe qui assemble l'ensemble des tests unitaires existants. En
rejouant cette "Test Suite" tu pourras t'assurer de rejouer
l'intégralité de tes tests unitaires et donc de contrôler au mieux les
éventuelles régressions qui surgiraient lors de ton développement. Pour
la mise en oeuvre de JUnit tu peux utiliser l'IDE Eclipse ou Netbeans.
Tous les 2 sont gratuits et intègrent parfaitement JUnit.
Personnellement, avec l'expérience, je préfère NetBeans en règle
générale, Eclipse étant un peu trop minimaliste à mon gôut et pas aussi
bien intégré que Netbeans contrairement à ce que l'on peut lire dans la
presse. Mais ceci est un autre débat !

Voilà pour l'essentiel, j'espère couvrir un peu ton problème. A bientôt !

Avatar
Hervé AGNOUX
Zouplaz wrote:

Bonjour, je voudrais bien essayer de me mettre à l'extreme programming.
J'ai lu un peu de littérature sur le sujet, mais j'ai du mal à voir la
mise en oeuvre concrète de l'utilisation intensives des tests.



Tu pourrais participer aux cordiales discussions sur
http://fr.groups.yahoo.com/group/xp-france/



Voila, pour reprendre mon exemple avec la variable privée socket
j'aimerais bien que quelqu'un me donne son point de vue et m'explique ce
qui cloche dans mon approche. Comment tester la bonne connexion au serveur
distant ?



Je ne suis pas un spécialiste de XP, je te réponds juste selon mon opinion.

Il faut que tu fasses différents choix politiques sur ta façon de réaliser
les tests. Aucun n'est parfait, aucun n'est intellectuellement pleinement
satisfaisant.

Pour démarrer sur des cas simples, je te conseille d'en rester aux tests
strictement unitaires ; ton exemple serveur nécessite l'existence d'un
réseau, d'un client ; c'est déjà un mini-système intégré.

Il ne faut pas hésiter à recomposer ton modèle selon ce que te suggère les
tests. Ici, par exemple, tu te retrouves avec un accés privé pour vérifier
une connexion. Il est privé, à cause de contraintes applicatives. Les
contraintes de test te suggèrent de la rendre publique. Dans ta démarche tu
exclues de le faire, parce que tu penses qu'il ne faut pas modifier une
fonction sous prétexte de la rendre testable.

Mais n'auras-tu pas un jour besoin, dans ton application, de tester la bonne
connexion ? N'auras-tu pas besoin de dire à l'utilisateur "Le réseau n'est
pas branché", ou quelque chose d'approchant ?

Si la réponse est oui, ton problème de test est résolu, tu rendras l'accès
public, et cette réflexion sur les tests t'aura fait progresser dans ton
appli.

Si la réponse est non, alors tu seras conduit à considérer cette fonction de
connexion comme une sorte de boîte noire, que l'extérieur n'a pas à
connaître, et donc pas à tester. Tu mettras en place des tests sur les
effets généraux de cette connexion, plutôt que sur son existence même ; par
exemple, sur une requête, ai-je la réponse ? Ta réflexion te conduira
plutôt vers l'utilité générale de la connexion client-serveur, qui pourrait
tout aussi bien être un peer-to-peer ou ce que tu voudras.

Voilà, je ne sais pas si je suis très clair. Je pense que au point où tu en
es, il faut surtout que tu voyes comment inclure les tests dans ta
réflexion de construction d'une appli. Au niveau technique... j'imagine que
tu sais comment rendre public un accès privé :-)

Cordialement.


--
Hervé AGNOUX
http://www.diaam-informatique.com

Avatar
Laurent Bossavit
Benoît,

On sent quelqu'un qui a pratiqué JUnit. (Pour ce qui est d'Extreme
Programming à proprement parler, voir mes remarques ci-dessous.) Sur
quel genre de projets ?

Maintenant d'un point de vue "eXtrem Programming", il convient de créer
d'abord le squelette de ta classe avec les méthodes qui doivent
retournées des valeurs par défaut. Lorsque ce squelette est prêt tu
écris l'ensemble de tes tests unitaires


Non, là on n'est déjà plus dans la démarche du "développement par les
tests" (TDD) que recommande Extreme Programming.

L'algorithme est le suivant:
- j'identifie *UNE* chose que mon programme doit pouvoir faire, par
exemple, l'addition de deux nombres;
- j'écris un test qui caractérise cette capacité; par exemple:
assertEquals(5, monObjet.additione(2,3) )
- si besoin est je créée les signatures de méthodes de façon à ce que ça
compile; par exemple:
class MaClasse {
public int additionne(int a, int b) { return -1;}
- j'exécute le test; je vérifie qu'il échoue
- j'implémente le code nécessaire pour passer le test
- j'effectue les remaniements (refactorings) si nécessaires pour rendre
le design résultant le plus simple possible

Je déroule la boucle ci-dessus pour *chaque* capacité du code, l'une
après l'autre. En aucun cas je n'écris plusieurs tests d'un seul coup.

A+

Avatar
Laurent Bossavit
Salut,

Bonjour, je voudrais bien essayer de me mettre à l'extreme programming.
J'ai lu un peu de littérature sur le sujet, mais j'ai du mal à voir la mise
en oeuvre concrète de l'utilisation intensives des tests.


Pour aller au-delà de la littérature, un bon moyen est de venir
rencontrer des gens qui pratiquent déjà. Par exemple à Paris:

http://xp-france.net/cgi-bin/wiki.pl?PraticiensDeParis

ou encore

http://xp-france.net/cgi-bin/wiki.pl?DojoDeveloppement

(On prépare également une conférence sur le sujet, cf. http://xpday.fr )

Plus de détails sur le problème concret que tu poses dans un autre
message.

Laurent

Avatar
Laurent Bossavit
Salut,

Voici le genre d'exemple qui me pose problème : Imaginons une classe
Serveur - [...] Pour savoir si la connexion a réussi ou pas :
- ici j'aurais un test du genre assertNotNull(serveur.socket)


Il faut qu'on soit d'accord sur ce que c'est qu'un "test".

Lorsque tu imagines les spécifications d'un programme, tu vas écrire (ou
simplement dire) des phrases du type suivant: "lorsque <contexte> et que
<évènement> alors on doit observer <conséquences>".

Par exemple:
"lorsque le serveur est lancé, si un client demande une connection
sur la même adresse et le même port, alors le client obtient une
connection; sinon il obtient une erreur"

Un test, c'est un exemple concret d'une situation couverte par une telle
spécification:

// lorsque le serveur est lancé...
Serveur s = new Serveur(1784);
s.lancer();
// et qu'un client demande une connection sur la même addresse/port
Socket s = connectTo("localhost", 1784);
// alors on obtient une connection
assertNotNull(s);

Tu remarqueras qu'ici le serveur est considéré comme "boîte noire", on
ne va pas lui ouvrir le ventre pour voir s'il a bien une socket. On se
contente de tester, de l'extérieur, ce que dit la spécification.

C'est ça un test:
1) on établit un contexte
2) on effectue une action
3) on constate les conséquences qu'a eu cette action

Les conséquences auxquelles on s'intéresse sont celles (et seulement
celles) qui permettent de répondre à la question: "les objets sur
lesquels j'ai invoqué ces actions répondent-ils à la spécification ?"

Dans l'exemple que tu donnes, je ne comprends pas quelles sont, dans ton
esprit, les spécifications de la classe Serveur. C'est ça qu'il faut
clarifier pour la tester efficacement.

(C'est bien dans ce sens-là que le développement par les tests s'avère
une méthode tout aussi rigoureuse que, disons, UML - on doit se forcer à
bien réfléchir à ce qu'on va faire, même si ce n'est que la quantité de
réflexion nécessaire à écrire *un* test.)

Autre conseil: la programmation distribuée (client-serveur) est un sujet
difficile, et le développement par les tests, quand on l'aborde la
première fois, est aussi un sujet difficile. Tu peux peut-être favoriser
ton apprentissage en mettant en oeuvre TDD sur des choses plus simples
pour commencer, et une fois que tu maîtriseras bien sa logique, tu
verras que l'application de TDD à la programmation distribuée coulera de
source.

Laurent

Avatar
Régis Décamps
Zouplaz wrote:
Bonjour, je voudrais bien essayer de me mettre à l'extreme programming.
J'ai lu un peu de littérature sur le sujet, mais j'ai du mal à voir la mise
en oeuvre concrète de l'utilisation intensives des tests.

Voici le genre d'exemple qui me pose problème : Imaginons une classe
Serveur - Imaginons encore que cette classe ouvre un socket, le socket
étant une variable privée.
Et enfin, imaginons que je veuille écrire un test qui vérifie la bonne
connection au serveur distant.

Pour savoir si la connexion a réussi ou pas :

- serveur.login(...)
- ici j'aurais un test du genre assertNotNull(serveur.socket)


Jusqu'à présent tu présentes un problème un problème de test, et non
d'XP. Je ne connais pas l'XP mais il me semble que l'une des règles est
"write tests first". En faisant cela, tu t'obliges à rendre publics les
méthodes Serveur.login(...) et Serveur.getSocket() (plutôt que la
variable Serveur.login)

Mais comme socket est une variable privée, je dois trouver une solution
pour y accéder ou alors ne pas réaliser de test dessus (cf.
http://www.artima.com/suiterunner/private.html)


Pourquoi la classe n'offre-t-elle pas un getter sur la socket? Comme
l'indique l'article que tu cites, cette méthode pourrait éventuellement
être 'protected' si tu ne vaux vraiment pas l'avoir 'public'.

Concrètement, dans un cas comme celui là j'ai beaucoup de mal à envisager
une solution dans laquelle je ne testerais pas la valeur de socket.
Je n'envisage pas de polluer la classe serveur avec une méthode public
isConnected(), celle ci n'ayant de raison d'exister que dans le cadre du
test,


Une autre façon d'écrire ton test est
testLogin() {
serveur.login(); // serveur étant initialisé par setUp();
assertTrue(serveur.isConnected());
}

A ce moment, ton IDE (au hasard, eclipse) se plaint de la non définition
des méthodes login() et isConnected(). En 2 clics tu les crées puis tu
les codes. Et oui, isConnected est publique et ne sert pour le moment
que pour tes tests. Personnellement, ça ne me gêne pas.

et je ne vois pas comment une série de tests peut être efficace si
elle ne porte que sur les méthodes et données publiques.

Si la classe serveur ne définie que 4 méthodes publiques :
serveur.login(...)
serveur.send(...)
serveur.receive(...)
serveur.loggout(...)

il me semble que les tests devront nécessairement porter sur les mécanismes
internes de la classe (chacune de ces 4 methodes faisant appel à de
nombreuses méthodes privées, imaginons les toutes plus complexes les unes
que les autres), sinon quel intérêt ?


Oui, c'est ce que je pense aussi, les tests unitaires doivent être
unitaires...

Merci d'avance


J'espère que des experts en XP nous donneront leur avis, le mien valant
ce qu'il vaut; je ne pratique pas l'(extreme) programming.

--
Régis

Avatar
Benoit EONNET
Laurent Bossavit wrote:
Benoît,

On sent quelqu'un qui a pratiqué JUnit. (Pour ce qui est d'Extreme
Programming à proprement parler, voir mes remarques ci-dessous.) Sur
quel genre de projets ?


Maintenant d'un point de vue "eXtrem Programming", il convient de créer
d'abord le squelette de ta classe avec les méthodes qui doivent
retournées des valeurs par défaut. Lorsque ce squelette est prêt tu
écris l'ensemble de tes tests unitaires



Non, là on n'est déjà plus dans la démarche du "développement par les
tests" (TDD) que recommande Extreme Programming.

L'algorithme est le suivant:
- j'identifie *UNE* chose que mon programme doit pouvoir faire, par
exemple, l'addition de deux nombres;
- j'écris un test qui caractérise cette capacité; par exemple:
assertEquals(5, monObjet.additione(2,3) )
- si besoin est je créée les signatures de méthodes de façon à ce que ça
compile; par exemple:
class MaClasse {
public int additionne(int a, int b) { return -1;}
- j'exécute le test; je vérifie qu'il échoue
- j'implémente le code nécessaire pour passer le test
- j'effectue les remaniements (refactorings) si nécessaires pour rendre
le design résultant le plus simple possible

Je déroule la boucle ci-dessus pour *chaque* capacité du code, l'une
après l'autre. En aucun cas je n'écris plusieurs tests d'un seul coup.

A+
Ok pour l'exemple. J'avais mis "eXtreme Programming" entre guillemets,

car je ne suis pas un grand expert de XP. D'un point de vue méthodologie
nous avons, a mon avis, 2 grandes méthodes actuellement qui s'opposent :
- la méthode MDA (Model Driven Architecture) : la philosophie étant que
seule "l'architecture à raison". Ceci signifie que c'est l'architecture
qui pilote le développement (donc utilisation massive d'UML et autres...).
- l'eXtreme Programing. On privilégie les recettes concrètes et
pratiques et rien que celles-ci. Il n'est pas interdit de modéliser,
mais il est inutile de trop modéliser. Ici c'est le code qui a raison.

A mon avis, bien que ces 2 méthodes apportent des idées tout à fait
intéressantes d'un certain point de vue, je constate qu'elles ne sont
pas adaptées à la réalité des cycles de développements. Quand on conçoit
une application, et donc une ou plusieurs classes, nous avons toujours
un minimum de méthodes qui sont définissables assez facilement. Cette
définition s'effectue soit à l'aide d'un outil de modélisation, soit
manuellement. Il en résulte que l'on a bien un ensemble de méthodes à
tester, même si ces méthodes ne sont pas représentatives du résultat
final que l'on souhaite atteindre pour la mise au point de notre classe.

Donc il vaut mieux modéliser un peu, ce qui génère le squelette minimal
de nos classes, concevoir les tests unitaires et enfin réaliser les
développements complémentaires et faire évoluer en parallèle les tests
unitaires et le modèle par reverse engineering. Cette approche est à
mi-chemin entre les 2 extrêmes MDA et XP.

PS : J'ai vue un lien sur XP qui tu as donné dans un autre mail
(http://xp-france.net/cgi-bin/wiki.pl?PraticiensDeParis). J'irai le voir
avec plaisir pour me faire une idée plus concrète sur les avantages et
iconvénients de cette méthodologie.

@++