Sigueme en Facebook Sigueme en Twitter Sigueme en Instagram Sigueme en Youtube
JC Mouse Bolivia
Index / Java / Pruebas funcionales con AssertJ Swing

Pruebas funcionales con AssertJ Swing

Autor jc mouse miércoles, septiembre 26, 2018

AssertJ Swing es una biblioteca para Java que proporciona una interfaz fluida para la realización automatizada de pruebas funcionales de Java UI Swing. AssertJ Swing se basa en las aserciones de tipos estándar JDK y se puede utilizar con JUnit o TestNG.

¿Que son las pruebas funcionales?

Las pruebas funcionales de software, son pruebas de tipo “Caja Negra” diseñadas para evaluar cada aspecto de los datos de entrada y salida de un software mediante el diseño y ejecución de modelos de prueba planificados para este fin; dicho de otro modo, este tipo de pruebas comprueba que el software hace lo que debe hacer y de la forma como se ha diseñado que lo haga.

En este post se realizara la creación y ejecución de un caso de prueba con AssertJ Swing

Herramientas

  • IDE Netbeans o Eclipse (usaremos Netbeans don’t hate me)
  • Java 8 o superior
  • Maven instalado y configurado
  • JUnit 4.12

COMENCEMOS!!!

1. El Proyecto

La aplicación que someteremos a prueba (una app trivial) trata sobre un conversor de números decimales a hexadecimales y octales, el proyecto llamado “Ejemplo AssertJ“, consta de una sola clase y su estructura es la siguiente:

assert netbeans

El código completo de esta aplicación se encuentra en la clase AppDemo.java y es el siguiente:

import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.text.DecimalFormat;
import javax.swing.BorderFactory;
import static javax.swing.BorderFactory.createEtchedBorder;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.TitledBorder;
import javax.swing.text.DefaultFormatterFactory;
import javax.swing.text.NumberFormatter;
/**
 * @see http://www.jc-mouse.net/
 * @author mouse
 */
public class AppDemo extends JFrame {

    private JButton btnConvertir;
    private JComboBox<String> cboOpcion;
    private JLabel jLabel1;
    private JLabel jLabel2;
    private JPanel jPanel1;
    private JPanel jPanel2;
    private JFormattedTextField txtNumero;
    private JTextField txtResultado;

    public AppDemo() {
        initComponents();
    }

    private void initComponents() {
        initLookAndFeel();
        setTitle("App Demo");
        GridBagConstraints gridBagConstraints;
        jPanel1 = new JPanel();
        jLabel1 = new JLabel();
        txtNumero = new JFormattedTextField();
        jLabel2 = new JLabel();
        cboOpcion = new JComboBox<>();
        btnConvertir = new JButton();
        jPanel2 = new JPanel();
        txtResultado = new JTextField();

        txtNumero.setName("numero");
        cboOpcion.setName("opcion");
        btnConvertir.setName("convertir");
        txtResultado.setName("resultado");

        getContentPane().setLayout(new GridBagLayout());
        jPanel1.setBorder(createEtchedBorder());
        jPanel1.setLayout(new GridBagLayout());

        jLabel1.setText("Número: ");
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.anchor = GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new Insets(0, 6, 0, 6);
        jPanel1.add(jLabel1, gridBagConstraints);

        txtNumero.setFormatterFactory(
                new DefaultFormatterFactory(new NumberFormatter(new DecimalFormat("#0"))));
        txtNumero.setPreferredSize(new Dimension(200, 22));
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.insets = new Insets(6, 0, 3, 6);
        jPanel1.add(txtNumero, gridBagConstraints);

        jLabel2.setText("Convertir a :");
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.insets = new java.awt.Insets(0, 6, 0, 6);
        jPanel1.add(jLabel2, gridBagConstraints);

        cboOpcion.setModel(new DefaultComboBoxModel<>(
                new String[]{"Hexadecimal", "Octal"}));
        cboOpcion.setPreferredSize(new Dimension(200, 22));
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 1;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.insets = new Insets(3, 0, 6, 6);
        jPanel1.add(cboOpcion, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new Insets(12, 12, 12, 12);
        getContentPane().add(jPanel1, gridBagConstraints);

        btnConvertir.setText("Convertir");
        btnConvertir.addActionListener((ActionEvent evt) -> {
            conversion();
        });
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        getContentPane().add(btnConvertir, gridBagConstraints);

        jPanel2.setBorder(BorderFactory.createTitledBorder(null, "<   Resultado   >",
                TitledBorder.CENTER, TitledBorder.TOP));
        jPanel2.setLayout(new GridBagLayout());

        txtResultado.setEditable(false);
        txtResultado.setFont(new Font("Tahoma", 1, 24));
        txtResultado.setHorizontalAlignment(JTextField.CENTER);
        txtResultado.setText("0");
        txtResultado.setPreferredSize(new Dimension(200, 32));
        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.anchor = GridBagConstraints.NORTHWEST;
        gridBagConstraints.insets = new Insets(6, 6, 6, 6);
        jPanel2.add(txtResultado, gridBagConstraints);

        gridBagConstraints = new GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 2;
        gridBagConstraints.insets = new Insets(12, 12, 12, 12);
        getContentPane().add(jPanel2, gridBagConstraints);

        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        pack();
        setLocationRelativeTo(null);
    }

    private void conversion() {
        if (txtNumero.getText().isEmpty()) {
            JOptionPane.showOptionDialog(this,
                    "Debe ingresar un valor numerico valido entre 1 y 99."
                            + " \nReinicie el programa para continuar.",
                    "Atención",
                    JOptionPane.OK_OPTION,
                    JOptionPane.INFORMATION_MESSAGE,
                    null,
                    new String[]{"No lo vuelvo hacer"},
                    null);
            txtNumero.setEnabled(false);
            cboOpcion.setEnabled(false);
            btnConvertir.setEnabled(false);
            return;
        }

        try {
            int numero = Integer.valueOf(txtNumero.getText());
            if (numero <= 0 || numero >= 100) {
                JOptionPane.showMessageDialog(this,
                        "Solo numeros entre 1 y 99. No se puede continuar.",
                        "Error", JOptionPane.ERROR_MESSAGE);
                txtResultado.setText("0");
                txtNumero.setText("0");
                return;
            }

            switch (cboOpcion.getSelectedItem().toString()) {
                case "Hexadecimal":
                    txtResultado.setText(String.valueOf(Integer.toHexString(numero)).toUpperCase());
                    break;
                case "Octal":
                    txtResultado.setText(String.valueOf(Integer.toOctalString(numero)).toUpperCase());
                    break;
            }
        } catch (NumberFormatException ex) {
            JOptionPane.showMessageDialog(this,
                    "Se ha producido un error [NumberFormatException]. No se puede realizar la conversión.",
                    "Error", JOptionPane.ERROR_MESSAGE);
            txtNumero.setText("0");
        }
    }

    private void initLookAndFeel() {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Windows".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException |
                InstantiationException |
                IllegalAccessException |
                UnsupportedLookAndFeelException ex) {
            System.exit(0);
        }
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            new AppDemo().setVisible(true);
        });
    }

}

No nos detendremos a explicar el código en su totalidad, lo que si es importante recalcar es que cuando trabajamos con AssertJ, debemos asignar un nombre a todos los controles que forman parte de la prueba, en este ejemplo son:

        txtNumero.setName("numero");
        cboOpcion.setName("opcion");
        btnConvertir.setName("convertir");
        txtResultado.setName("resultado");

2. Maven

Debemos agregar al proyecto, la siguiente dependencia:

<dependency>
  <groupId>org.assertj</groupId>
  <artifactId>assertj-swing-junit</artifactId>
  <version>3.8.0</version>
</dependency>

Crea el proyecto en tu IDE y ejecútalo, podrás obtener algo como esto:

hexadecimal octal java

3. Preparando el Test

Si estas utilizando Netbeans, las clases destinadas a la realización de tests se alojan en la sección denominada “Paquete de pruebas” en la pestaña “Projects” o en la carpeta “test” desde la pestaña “Files”

paquete de pruebas Test

Agrega una nueva clase con el nombre de “TestApp” ademas de crear una estrctura de paquetes igual al de tu proyecto “org.example.ejemploassertj”. Es  en esta clase donde se colocan cada uno de las pruebas a las que se someterá nuestro software, pero antes, debemos preparar la clase para funcionar con AssertJ, lo hacemos de la siguiente manera:

import org.assertj.swing.edt.FailOnThreadViolationRepaintManager;
import org.assertj.swing.edt.GuiActionRunner;
import org.assertj.swing.fixture.FrameFixture;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;

/**
 * @see http://www.jc-mouse.net/
 * @author mouse
 */
public class TestApp {
    private FrameFixture window;

    /**
     * Constructor de clase
     */
    public TestApp() {}

    /**
     * Fuerza a una prueba a fallar si el acceso a los componentes de la GUI
     * no se realiza en el EDT (Event Dispatch Thread)
     */
    @BeforeClass
    public static void setUpOnce() {
        FailOnThreadViolationRepaintManager.install();
    }

    /**
     * Inicializa los dispositivos de prueba, se ejecuta cada vez 
     * que se ejecute un método de prueba
     */
    @Before
    public void setUp() {
        AppDemo frame = GuiActionRunner.execute(() -> new AppDemo());
        window = new FrameFixture(frame);
        window.show();
    }

    /**     
     * Limpia los recursos utilizados después de ejecutar cada método de prueba
     * y libera el bloqueo de teclado y moyse para la siguiente prueba     
     */
    @After
    public void tearDown() {
        window.cleanUp();
    }
    
}

Con el código anterior nos aseguramos que AssertJ reconozca nuestra aplicación y pueda realizar los test automáticamente para al finalizar liberar los recursos utilizados.

4. Las pruebas

Como dijimos en un principio, este tipo de pruebas requiere una previa planificación de los datos que ingresaremos y el resultado esperado, el tester no ve el código fuente (clases, métodos, etc) y se guía por la documentación del programador (tipo de datos de entrada, datos de salida, longitud, formato, etc).

Nuestro software en cuestión sera sometido a 5 pruebas:

  • Conversión Octal: Se ingresara un un numero decimal (45) y se espera que el software realice la conversión a Octal (55), al final se comparara el valor obtenido con un valor ya conocido (55) y si son iguales, paso la prueba
  • Conversión Hexadecimal: Se ingresara un numero decimal (26) y se espera que el software realice la conversión a Hexadecimal (1A), al final se comparara el valor obtenido con un valor ya conocido (1A) y si son iguales, paso la prueba
  • Numero fuera de rango: La aplicación solo puede realizar la conversión para números comprendidos entre el 1 y el 99, si se introduce un numero fuera de rango (226), se espera que el sistema lance una advertencia y detenga la conversión reiniciando los controles a cero “0”.
  • Valor numérico no valido: Si se introduce una cadena vacía, el sistema mostrara una ventana de alerta y se bloqueara. El texto del botón de alerta es “No lo vuelvo hacer”.
  • NumberFormatException: Si se produce una excepción, el sistema notificara del error y no realizara la conversión reiniciando el textbox a cero “0”.

5.  Completando la clase TestApp

Una vez establecidos los casos de prueba, debemos plasmar estos en código utilizando los métodos propios de AssertJ y JUnit.

El código completo de la clase TestApp, es:

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.swing.core.matcher.JButtonMatcher.withName;
import static org.assertj.swing.core.matcher.JButtonMatcher.withText;
import org.assertj.swing.edt.FailOnThreadViolationRepaintManager;
import org.assertj.swing.edt.GuiActionRunner;
import org.assertj.swing.fixture.FrameFixture;
import org.junit.After;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
/**
 * @see http://www.jc-mouse.net/
 * @author mouse
 */
public class TestApp {

    private FrameFixture window;

    /**
     * Constructor de clase
     */
    public TestApp() {}

    /**
     * Fuerza a una prueba a fallar si el acceso a los componentes de la GUI
     * no se realiza en el EDT (Event Dispatch Thread)
     */
    @BeforeClass
    public static void setUpOnce() {
        FailOnThreadViolationRepaintManager.install();
    }

    /**
     * Inicializa los dispositivos de prueba, se ejecuta cada vez 
     * que se ejecute un método de prueba
     */
    @Before
    public void setUp() {
        AppDemo frame = GuiActionRunner.execute(() -> new AppDemo());
        window = new FrameFixture(frame);
        window.show();
    }

    /**     
     * Limpia los recursos utilizados después de ejecutar cada método de prueba
     * y libera el bloqueo de teclado y moyse para la siguiente prueba     
     */
    @After
    public void tearDown() {
        window.cleanUp();
    }
    
    @Test
    public void ConversionOctal() {                
        window.textBox("numero").enterText("45");
        window.comboBox("opcion").selectItem("Octal");        
        window.button(withName("convertir")).click();       
        //realiza la comparación de resultados
        assertThat(window.textBox("resultado").text()).isEqualTo("55");        
    }
    
    @Test
    public void conversionHexadecimal() {                
        window.textBox("numero").enterText("26");
        window.comboBox("opcion").selectItem("Hexadecimal");        
        window.button(withName("convertir")).click();         
        //realiza la comparación de resultados
        assertThat(window.textBox("resultado").text()).isEqualTo("1A");        
    }
     
    @Test
    public void numeroFueraDerango() {
        window.textBox("numero").enterText("226");
        window.comboBox("opcion").selectItem("Hexadecimal");
        window.button(withName("convertir")).click();
        //cierra la ventana de alerta
        window.dialog().button().click();
        //verifica que controles se reinicien a cero "0"
        assertThat(window.textBox("numero").text()).isEqualTo("0");     
        assertThat(window.textBox("resultado").text()).isEqualTo("0");     
    }

    @Test
    public void valorNumericoNoValido() {
        window.textBox("numero").enterText("");
        window.comboBox("opcion").selectItem("Octal");        
        window.button("convertir").click();
        //Cierra ventana de alerta
        window.dialog().button(withText("No lo vuelvo hacer")).click();
        //se comprueba que controles esten deshabilitados
        window.textBox("numero").requireDisabled();
        window.comboBox("opcion").requireDisabled();
        window.button("convertir").requireDisabled();
    }
    
    @Test
    public void errorCritico() {
        window.textBox("numero").enterText("98899979941993239211992309990991");
        window.comboBox("opcion").selectItem("Hexadecimal");
        window.button("convertir").click();
        //cierra ventana de alerta
        window.dialog().button().click();        
        //verifica que textbox se haya reiniciado a cero "0"
        assertThat(window.textBox("numero").text()).isEqualTo("0");    
    }

}

6. Ejecución

Para ejecutar la clase TestApp.java, clic derecho sobre el archivo -> Test File, esperamos que se realicen las pruebas y si pasamos correctamente cada uno de los test, obtendremos esto:

pruebas funcionales de software

El proceso se realiza de forma automática y secuencial, si se detecta un error el proceso se detiene caso contrario se ejecutan todas las pruebas hasta su conclusión.

Enjoy!!!

Tags

Si te ha gustado podrías compartirlo o dejar un comentario. ¡Muchas gracias!
Autor: JC Mouse

Yo soy yo :) JC Mouse, Soy orgullosamente boliviano soy fundador y CEO de la web jc-Mouse.net uno de las pocas web en emprendimiento y tecnología en Bolivia.

Toda la información que encuentres en este sitio es y sera completamente gratis siempre, puedes copiar, descargar y re-publicar si así lo deseas en otros blogs o sitios web, solo te pido a cambio que dejes una referencia a esta web. Esto nos ayuda a crecer y seguir aportando. Bye

Enjoy! :)

También Te Podría Interesar

Crear PopupMenu (Ventana Emergente)

Crear PopupMenu (Ventana Emergente)

En este tutorial crearemos una aplicación android que nos permitirá abrir un PopupMenu de donde podremos seleccionar una...

jFace – Crea retratos hablados

jFace – Crea retratos hablados

La policia, el FBI u otros organismos similares utilizaban a dibujantes para realizar el “retrato hablado” d...

Ejecutar JAR desde Visual Basic .NET (Lanzadores)

Ejecutar JAR desde Visual Basic .NET (Lanzadores)

Cuando queremos presentar un programa hecho en java y darle un toque de profesionalidad, a veces no queremos que el clie...

Compresión y descompresión de archivos con GZIP

Compresión y descompresión de archivos con GZIP

En este post veremos un ejemplo de como comprimir y descomprimir archivos con el método de compresión GZIP  y el paquete...

Introducción a Scene Builder y MVC (Parte II)

Introducción a Scene Builder y MVC (Parte II)

Segunda parte del tutorial [Introducción a Scene Builder y MVC (Parte I)]. En esta segunda parte, completaremos el diseñ...

Sistema de gestión de stock – El Controlador (Parte 5)

Sistema de gestión de stock – El Controlador (Parte 5)

Para terminar el tutorial, debemos unir tanto la VISTA como el MODELO y para eso esta el CONTROLADOR. o.O El controlador...

Comparte lo que sabes

Categorias

Últimas entradas

Pyodide es un proyecto experimental de Mozilla  que proporciona un intérprete de Python que se ejecuta completamente en...

PicarOS Diego es una distribución GNU/Linux basada en Debian enfocada principalmente en la educación dentro del aula rec...

Windows XP fue lanzado oficialmente el 25 de octubre de 2001, han pasado ya 17 años y 7 meses convirtiendo así a XP uno...

Java proporciona Collection Framework, que define varias clases e interfaces para representar un grupo de objetos como u...

Android Bolivia

MAUS