Est-il possible de tester une classe Swing avec l'API de test JUnit sous Eclipse ?
Bonjour à tous,
je ne parviens pas à trouver la réponse à ma question dans la FAQ.
Savez vous si il est possible de tester une classe Swing dans Eclipse Kepler Service Release 2 avec JUnit-4.7 ?
Je cherche à tester des GUI (Graphic User Interface) écrites avec Java jdk1.7.0_65 et notamment son API Swing.
J'ai une classe qui m'ouvre une JFrame contenant un JPanel et ses composants. Lorsque je teste cette classe dans une classe applicative (avec méthode main(...)) en ouvrant un Thread EDT, tout fonctionne parfaitement.
La fenêtre s'affiche et je vois tout ce qui doit apparaitre à l'écran.
Je souhaiterais savoir si il est possible de faire la même chose dans une méthode d'un Test JUnit-4.7 sous Eclipse Kepler.
J'utilise le JUnit dans Eclipse Kepler (JUnit-4.7.jar déclaré dans mes librairies) et j'essaie de faire un JUnit Test case comme pour les classes non graphiques.
Le test JUnit semble se dérouler normalement (barre verte) avec une classe graphique Swing, mais je ne parviens pas à afficher la JFrame contenant un JPanel et ses composants.
Je suppose que le Thread EDT gérant l'affichage des composants graphiques Swing peut poser problème mais je suis trop court en Java pour pouvoir me dépêtrer de ce souci.
Quelqu'un aurait-il trouvé une solution dans Eclipse Kepler ? Merci d'avance.
Cordialement,
Daniel Lévy
dany.levy@free.fr
Incompatibilité entre le Thread EDT et JUnit ?
Citation:
Envoyé par
tchize_
La classe robot, selon moi, c'est overkill pour du unit test. Ca va simuler des clic et des entrées souris via le système natif de l'OS. Autrement dit, si ça clic à coté, tu peux balancer tout ton test au final dans un email, effacer des fichiers dans l'exploreur, etc. Assez dangereux et de toutes façons très difficile d'avoir un feedback pour le tapper dans un Assert.
Personellement, quand je dois tester que des composants swings se comportent correctement, c'est souvent des tests de synchro. Genre "a tiens, si je sélectionne tel élement dans la liste, tel et tel JTextField doivent s'activer". Et ça c'est assez facile à tester. Tu instancie la JFrame, tu crée un getter pour avoir la JList et les textfields, et ton test deviens
Code:
1 2 3
| Fenetre maFenetre = new Fenetre();
maFenetre.getLaListe().setSelectedIndex(3);
assetEquals("Tartempion",maFenetre.getNameField().getText()); |
Pour des chose un peu plus complexe, genre "HA si je clique droit sur le JLabel, une JPopup dois apparaitre", c'est plus complexe. Tu peux facilement balancer l'event via processMouseEvent() sur le composant. Pour détecter l'affichage de la JPopup, tu pourrais utiliser powerMockito pour intercepter les new sur JPopupMenu et en conclure que, oui, on a bien lancer une popup. Ou diviser mieux ton code pour que la popup soit gérée par une classe à part facilement mockable.
Pour le reste, les interactions complexe, rien ne vaut l'oeil du testeur au final.
Merci beaucoup,
en fait, ma question portait plus sur le problème du Thread EDT pour l'affichage graphique. Quand je fais une classe applicative avec une méthode main comme suit :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public static void main(final String[] args) {
/* Utilise le Event Dispatch Thread (EDT). */
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
/* Création d'une JFrame. */
final JFrame frame = new JFrame();
/* Création d'un JPanel. */
final JPanel panel = new JPanel();
/* Coloration et dimensionnement du panel. */
panel.setBackground(Color.BLUE);
panel.setPreferredSize(new Dimension(900, 700));
panel.setMinimumSize(new Dimension(900, 700));
/* Passe un GridBagLayout au ContentPane de la JFrame . */
frame.getContentPane().setLayout(new GridBagLayout());
/* Ferme l'application lorsque l'on ferme la fenêtre. */
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
/* Ajout du JPanel à la JFrame. */
frame.getContentPane().add(panel);
/* Met en place la JFrame. */
frame.pack();
/* Dimensionne la JFrame. */
frame.setSize(1000, 800);
/* JFrame redimensionnable. */
frame.setResizable(true);
/* JFrame centrée au mileu de l'écran. */
frame.setLocationRelativeTo(null);
/* Affichage de la JFrame. */
frame.setVisible(true);
} // Fin de Run().____________________________
}); // Fin de new Runnable()._______________________________
} // Fin de main(...)._________________________________________________ |
Je lance dans Eclipse cette application. Tout fonctionne parfaitement et la JFrame s'affiche.
Je ne parviens à faire la même chose au sein d'une méthode d'un test Junit. L'affichage semble impossible. Je suppose qu'il y a une incompatibilité entre l'utilisation du Thread EDT et l'API JUnit mais je n'en suis pas sûr.
Je voulais donc savoir si il y avait une astuce permettant d'afficher du Swing dans un test JUnit (ouvrir un nouveau Thread, utilisation d'une classe SwingWorker, ....).
Merci d'avance.
3 pièce(s) jointe(s)
A Mon avis, il y a conflit entre l'affichage d'un composant Swing et JUnit
Citation:
Envoyé par
tchize_
Ce n'est pas caricatural et part bien d'expériences personnelle. La classe Robot ne fait que créer des évenements systèmes et ceux si peuvent aussi bien arriver sur une de vos fenètre qu'à coté à cause d'un bug. Et quand le test et un test de drag and drop, ça se résume à un drag and drop "à coté". En l'occurence dans mon cas à l'époque c'est tombé dans le eclipse derrière qui a fait disparaitre un dossier du code pour le faire disparaitre dans un sous dossier obscur. C'est allé trop vite pour voir ce qui se passait et j'ai perdu une demi journée à remettre le projet en état...
La plupart des tests n'ont pas besoin de Robot car on peut directement injecter les évènements sur la frame, qui dispatchera elle même aux différents composants. Du code swing c'est du code comme un autre, ce sont des objets avec des méthodes, et c'est testable de la même manière: on appelle les méthode, on met des mocks où il faut et on vérifie ce qui en sort.
Bref, je parlais pas de faire travailler Robots comme un gremlings, mais d'éviter un triple travail de test à savoir:
Tester que le code principal n'a pas de soucis (le but du unit test)
Tester que le code de test ne pourra jamais dérapper (c'est la partie que je trouve overkill dans Robots selon moi)
S'assurer pendant les 20 minutes que tourne le test qu'aucune andouille de collègue ne touchera votre clavier pour vous montrer un truc
Bonjour Tchize_
J'ai réalisé ce que tu me préconisais.
J'ai fait une première classe AfficheurFrame chargée d'instancier, décorer et afficher dans le Thread EDT la JFrame :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
package levy.daniel.application.presentation.desktop.jframe;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.border.BevelBorder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* class AfficheurFrame :<br/>
* Classe applicative permettant d'instancier et d'afficher une JFrame.<br/>
* - Possède une méthode main(...) permettant
* de la lancer comme une application Java.<br/>
* - Affiche la JFrame dans l'Event Dispatch Thread (EDT).<br/>
* <br/>
*/
public final class AfficheurFrame {
// ************************ATTRIBUTS************************************/
/**
* LOG : Log : Logger pour Log4j (utilisant commons-logging).
*/
@SuppressWarnings("unused")
private static final Log LOG = LogFactory.getLog(AfficheurFrame.class);
// *************************METHODES************************************/
/**
* method CONSTRUCTEUR AfficheurFrame() :<br/>
* CONSTRUCTEUR D'ARITE NULLE.<br/>
* Constructeur private pour bloquer l'instanciation externe.<br/>
* <br/>
*/
private AfficheurFrame() {
super();
} // Fin de CONSTRUCTEUR D'ARITE NULLE.________________________________
/**
* method creerEtAfficherFrame() :<br/>
* Instancie, décore et affiche une JFrame.<br/>
* <br/>
*
* @return : JFrame : JFrame affichée.<br/>
*/
public static JFrame creerEtAfficherFrame() {
/* Instanciation d'une JFrame. */
final JFrame frame = new JFrame();
/* Création d'un JPanel pour décorer la JFrame. */
final JPanel panel = new JPanel();
panel.setLayout(new GridBagLayout());
/* Création d'un JLabel pour égayer le JPanel. */
final JLabel label = new JLabel("CA MARCHE");
label.setHorizontalAlignment(SwingConstants.CENTER);
label.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED));
label.setBackground(Color.WHITE);
label.setOpaque(true);
label.setPreferredSize(new Dimension(100, 40));
label.setMinimumSize(new Dimension(100, 40));
/* Ajout du JLabel au JPanel. */
panel.add(label, new GridBagConstraints());
/* Coloration et dimensionnement du panel. */
panel.setBackground(Color.BLUE);
panel.setPreferredSize(new Dimension(150, 100));
panel.setMinimumSize(new Dimension(150, 100));
/* Passe un GridBagLayout au ContentPane de la JFrame . */
frame.getContentPane().setLayout(new GridBagLayout());
/* Ajout du JPanel à la JFrame. */
frame.getContentPane().add(panel, new GridBagConstraints());
/* Ferme l'application lorsque l'on ferme la fenêtre. */
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
/* Met en place la JFrame. */
frame.pack();
/* Dimensionne la JFrame. */
frame.setSize(200, 150);
/* JFrame redimensionnable. */
frame.setResizable(true);
/* JFrame centrée au mileu de l'écran. */
frame.setLocationRelativeTo(null);
/* Affichage de la JFrame. */
frame.setVisible(true);
return frame;
} // Fin de creerFrame().______________________________________________
/**
* method main() :<br/>
* Point d'entrée de l'application.<br/>
* Instancie, décore et affiche une JFrame.<br/>
* - Ouvre un Thread EDT dédié à l'affichage.<br/>
* <br/>
*
* @param args : String[].<br/>
*/
public static void main(final String[] args) {
/* Utilise le Event Dispatch Thread (EDT). */
SwingUtilities.invokeLater(new Runnable() {
/**
* {@inheritDoc}
*/
@Override
public void run() {
/* Instancie, crée et affiche la JFrame dans le Thread EDT. */
creerEtAfficherFrame();
} // Fin de Run().____________________________
}); // Fin de new Runnable()._______________________________
} // Fin de main(...)._________________________________________________
} // FIN DE LA CLASSE AfficheurFrame.---------------------------------------- |
Lorsqu'on lance cette application (méthode main(..), on obtient l'affichage suivant :
Pièce jointe 171642
J'ai ensuite réalisé la classe de test JUnit AfficheurFrameTest comme tu me l'avais indiqué :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
package levy.daniel.application.presentation.desktop.jframe;
import static org.junit.Assert.*;
import java.lang.reflect.InvocationTargetException;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.junit.Test;
/**
* class AfficheurFrameTest :<br/>
* Test JUnit de la classe AfficheurFrame.<br/>
* <br/>
*/
public class AfficheurFrameTest {
// ************************ATTRIBUTS************************************/
/**
* frameATester : JFrame :<br/>
* JFrame à tester créée par un AfficheurFrame.<br/>
*/
private static JFrame frameATester;
/**
* LOG : Log :
* Logger pour Log4j (utilisant commons-logging).
*/
@SuppressWarnings("unused")
private static final Log LOG = LogFactory.getLog(AfficheurFrameTest.class);
// *************************METHODES************************************/
/**
* method testCreerEtAfficherFrame() :<br/>
* TEST JUnit.<br/>
* Teste l'affichage de la JFrame.<br/>
* <br/>
* @throws InterruptedException
* @throws InvocationTargetException
*/
@Test
public void testCreerEtAfficherFrame()
throws InvocationTargetException, InterruptedException {
/* L'invocation de invokeAndWait(...) au lieu de invokeLater(...)
* laisse à Swing le temps de consommer les évènement éventuellement
* créés par l'apparition de la fenêtre en se remettant à la fin.*/
SwingUtilities.invokeAndWait(new Runnable() {
/**
* {@inheritDoc}
*/
@Override
public void run() {
/* CREATION DE LA JFrame. */
AfficheurFrameTest.setFrameATester(
AfficheurFrame.creerEtAfficherFrame());
} // Fin de Run().____________________________
}); // Fin de new Runnable()._______________________________
/* TESTS UNITAIRES. */
assertNotNull("La JFrame ne doit pas être null : ", frameATester);
assertTrue("La JFrame doit être visible : ", frameATester.isVisible());
} // Fin de testCreerEtAfficherFrame().________________________________
/**
* method getFrameATester() :<br/>
* Getter de la JFrame à tester créée par un AfficheurFrame.<br/>
* <br/>
*
* @return frameATester : JFrame.<br/>
*/
public static final JFrame getFrameATester() {
return frameATester;
} // Fin de getFrameATester()._________________________________________
/**
* method setFrameATester(
* JFrame pFrameATester) :<br/>
* Setter de la JFrame à tester créée par un AfficheurFrame.<br/>
* <br/>
*
* @param pFrameATester : JFrame :
* valeur à passer à frameATester.<br/>
*/
public static final void setFrameATester(
final JFrame pFrameATester) {
frameATester = pFrameATester;
} // Fin de setFrameATester(
// JFrame pFrameATester)._____________________________________________
} // FIN DE LA CLASSE AfficheurFrameTest.------------------------------------ |
Le test JUnit se déroule parfaitement (barre verte dans Eclipse). Mais la fenêtre apparaît de manière très fugace puis disparait. Elle ne reste pas affichée à l'écran.
Il ne semble pas possible d'obtenir le même comportement de la JFrame dans un test JUnit que lorsque l'on utilise l'application AfficheurFrame.
Je pense donc qu'il y a bien un conflit entre l'API JUnit et l'affichage d'un composant Swing.
En revanche, ça ne gêne en rien pour tester avec JUnit.