Cómo hacer testing automático de un applet Java

0
12279

Índice de contenidos

1. Introducción

Los Applets son aplicaciones Java que se ejecutan dentro de un navegador con una serie de restricciones de seguridad, como por ejemplo no poder acceder al sistema de ficheros local. Si bien este tipo de aplicaciones ya está en desuso gracias a los avances en HTML5, CSS3, y JavaScript, pueden ser muy útiles para complementar la funcionalidad de nuestras aplicaciones Web, ya que permiten hacer cosas que con JavaScript son muy complicadas o incluso imposibles (acceder al sistema de ficheros, manipulación de certificados, gestión de dispositivos conectados al ordenador, …).

En este tutorial vamos a ver como podemos hacer tests automáticos de un applet teniendo en cuenta sus particularidades de ejecución.

Para el tutorial he preparado el siguiente repositorio de GitHub https://github.com/alejandropg/testing-applet

En el repositorio podemos encontrar la siguiente estructura:

testing-applet
|---- demo/
|---- dria/
|---- fest/
|---- log/
|---- src/
|-- LICENSE
|-- README.md
|-- build.gradle
|-- generate-demo-certs.sh
`-- settings.gradle

Donde podemos destacar:

  • demo/ – contienen los ejemplos para probar las librerías, así como los tests de sistema que prueban el ciclo completo levantando un FireFox y ejecutando los tests automáticos con SeleniumHQ. Nada que se encuentre dentro de este directorio debería acabar en el código de producción, es decir sólo son ejemplos de cómo usar el código que se encuentra en el resto de directorios.
  • dria/Driven Rich Internet Application. Pequeña librería de testing, desarrollada aquí para simular la navegación e interacción con el applet. Tanto atacando a la clase del applet directamente con Swing, como atacando al applet dentro de un navegador a través de LiveConnect y JavaScript.
  • fest/FEST es un conjunto de librerías para facilitar el testing. En este directorio encontraremos unas clases que nos ayudarán a probar el applet usando estas librerías, en concreto FEST Swing para simular la navegación e interacción con el applet.
  • log – Mini librería de log. No usamos ninguna de las habituales (Log4j, …) ya que como el applet viaja por Internet hasta el navegador del usuario, queremos que ocupe lo mínimo imprescindible, así que intentaremos evitar a toda costa las librerías de terceros.
  • src/ – algunos ficheros de configuración para generar los certificados.
  • generate-demo-certs.sh – script que usaremos para generar los certificados necesarios para firmar el applet.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil MacBook Pro 15’’ (2.3 GHz Intel i7, 16GB 1600 Mhz DDR3, 500GB Flash Storage).
  • NVIDIA GeForce G7 750M
  • Sistema Operativo: Mac OS X Lion 10.9.4
  • OpenSSL 0.9.8y
  • Node v0.10.26
  • Firefox 31.0
  • Java Virtual Machine (JVM) 1.8.0_11
  • FEST Swing 1.2.1
  • SeleniumHQ 2.41.0

3. Generando los certificados para firmar el Applet

Para que un applet se pueda ejecutar en el navegador del usuario, aquél debe estar firmado. Esto es necesario para que navegador pueda identificar al creador del applet y así comprobar que es de confianza. Si este certificado viene expedido por una CA (Certificate Authority – Autoridad Certificadora) reconocida por el navegador, este permitirá la ejecución del applet. Sin embargo si la CA no es reconocida por el navegador, este mostrará un mensaje de alerta al usuario para que acepte explícitamente la ejecución del applet, ya que el navegador no confiará en él.

Para firmar el applet tenemos dos opciones:

  1. Usar un certificado expedido por una CA reconocida, como GoDaddy, VeriSign, Thawte, …
  2. Generar nosotros nuestra propia CA y añadirla al navegador para que la reconozca.

Para un entorno de producción evidentemente la opción recomendada es la 1., principalmente por los siguientes motivos:

  • Sobre todo porque si no se reconoce la CA, tal como hemos comentado antes, el navegador mostrará al usuario una alerta indicando que no puede confiar en el applet ya que no se reconoce la firma. Si el usuario no es experimentado esto puede causar rechazo o desconfianza en nuestra aplicación, y si el usuario no acepta el aviso del navegador nuestra aplicación no se ejecutará correctamente.
  • Así no tenemos que tocar la configuración del navegador de nuestros usuarios. Cosa que además en muchas ocasiones ni siquiera es posible (en una intranet podemos tener acceso a la configuración de todos los clientes, pero en una aplicación destinada a Internet esto es impensable).

Pero para un entorno de desarrollo, es perfectamente válida la opción 2., incluso recomendable ya que podemos simular pruebas, por ejemplo que el certificado está caducado.

Aquí vamos a ver como utilizar la opción 2. y generar nuestra propia CA y nuestros propios certificados.

  1. Vamos a crear un directorio para trabajar con los certificados.
    BASE=`pwd`
    CERTS_DIR="./certs"
    KEYSTORE="testing-applet.jks"
    STOREPASS="autentia"
    VALDAYS="3650"
    
    mkdir -p $CERTS_DIR
    cd $CERTS_DIR
    
  2. Ahora preparamos nuestra propia CA:
    mkdir -p ./demoCA/private
    mkdir -p ./demoCA/newcerts
    touch ./demoCA/index.txt
    echo 01 > ./demoCA/serial
    
    openssl genrsa -out ./demoCA/private/cakey.pem 2048
    openssl req -new -x509 -days $VALDAYS -key ./demoCA/private/cakey.pem -out ./demoCA/cacert.pem -config $BASE/src/main/config/demoCA.config
    

    Aquí destacamos como con el primer comando opensslestamos creando la clave de la CA y en el segundo comando openssl estamos creando el certificado propio de la CA. Recordad que un certificado tiene dos claves, la privada que nunca se debe dar, y la pública que es la que mandaremos al resto del mundo para que se comuniquen con nosotros.

    Con esto queda preparada la CA.

  3. Ahora vamos a importar la clave pública de la CA en el repositorio de claves de nuestra JVM para que está confíe en esta CA.
    keytool -keystore $KEYSTORE -storepass $STOREPASS -importcert -alias demoCA -file ./demoCA/cacert.pem -noprompt
    

    Se supone que la clave pública nos la habrá mandado la CA por correo o la habremos conseguido por cualquier otro medio. En nuestro caso estamos accediendo directamente al fichero que hemos generado en el paso anterior, pero esto no tendría mucho sentido en la vida real.

    En este punto también debemos añadir la clave pública de la CA a la JVM que ejecutará el applet. Esto es imprescindible para que el navegador permita la ejecución del applet. Para ello hacemos:

    BROWSER_CACERTS="/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/security/cacerts"
    sudo keytool -keystore "$BROWSER_CACERTS" -storepass changeit -importcert -alias demoCA -file ./demoCA/cacert.pem -noprompt
    

    Ojo aquí porque el valor de la variable $BROWSER_CACERTS va a depender mucho del Sistema Operativo donde estemos. En el ejemplo que os pongo sería el sitio típico en OS X.

  4. Ahora vamos a generar un par de certificados nuestros. Estos certificados son los que usaremos para nuestras cosas, como por ejemplo firmar nuestro applet.
    DNAME="C=ES, ST=Madrid, L=SanFernandoDeHenares, O=Autentia, OU=Tutoriales, CN=Testing Applet Cert"
    DNAME1="$DNAME"1
    DNAME2="$DNAME"2
    keytool -keystore $KEYSTORE -storepass $STOREPASS -keypass $STOREPASS -alias demoCert1 -dname "$DNAME1" -genkeypair -ext bc=ca:true
    keytool -keystore $KEYSTORE -storepass $STOREPASS -keypass $STOREPASS -alias demoCert2 -dname "$DNAME2" -genkeypair
    

    Por ahora sólo son certificados autofirmados (por ahora no tienen ninguna relación con la CA). Y los hemos generado con la propia herramienta keytool de Java.

  5. Generamos una petición de firma para enviar a la CA. Es decir, vamos a preparar un fichero (demoCert1.csr) que mandaremos a la CA para que esta nos firme, y así valide, nuestro certificado.
    keytool -keystore $KEYSTORE -storepass $STOREPASS -alias demoCert1 -certreq -file demoCert1.csr
    
  6. Ahora la CA firma la petición que hemos generado en el paso anterior.
    openssl ca -in demoCert1.csr -out demoCert1.pem.cer -config $BASE/src/main/config/demoCA.config -batch
    

    La CA nos devuelve el certificado firmado (el fichero demoCert1.pem.cer)

  7. Una vez recibimos el certificado firmado por la CA sólo nos queda importarlo en nuestro almacén de claves, para así poder usarlo para firmar nuestro applet.
    keytool -keystore $KEYSTORE -storepass $STOREPASS -alias demoCert1 -importcert -file demoCert1.pem.cer
    

Para que todo el proceso quede un poco más claro, presentamos un gráfico donde se ve como interactuamos con la CA, y así se vea quién hace qué.

Generadno el certificado para el applet
Generadno el certificado para el applet

Además en el script generate-demo-certs.sh tenéis toda la secuencia de pasos para reproducirla de forma sencilla en vuestro entorno.

 

4. Creando un Applet de ejemplo

Ahora vamos a crear un sencillo applet de ejemplo. El applet tendrá un campo de entrada, un botón y campo de salida, de forma que al dar al botón todo el texto que se encuentre en el campo de entrada se añadirá en el campos de salida.

Algo de este estilo.

Applet sencillo
Applet sencillo

El código lo encontramos en el fichero demo/applet/src/main/java/com/autentia/applet/DemoApplet.java

public class DemoApplet extends JApplet implements ActionListener {

    private JTextArea inTxtArea;
    private JTextArea outTxtArea;

    @Override
    public void init() {
        super.init();

        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    setName("DemoApplet");
                    createGUI();
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void createGUI() {
        getContentPane().setName("contentPane");
        setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));

        inTxtArea = buildInTxtArea();
        outTxtArea = buildOutTxtArea();

        add(inTxtArea);
        add(Box.createVerticalStrut(20));
        add(buildAddTextButton());
        add(Box.createVerticalStrut(20));
        add(outTxtArea);
    }

    private JTextArea buildInTxtArea() {
        final JTextArea txtArea = new JTextArea();
        txtArea.setName("inTxtArea");
        txtArea.setText("Hola Mundo!");
        return txtArea;
    }

    private JTextArea buildOutTxtArea() {
        final JTextArea txtArea = new JTextArea();
        txtArea.setName("outTxtArea");
        txtArea.setEditable(false);
        return txtArea;
    }

    private JButton buildAddTextButton() {
        final JButton btn = new JButton("Añadir el texto");
        btn.setName("btn");
        btn.setAlignmentX(Component.CENTER_ALIGNMENT);
        btn.setActionCommand("addText");
        btn.addActionListener(this);
        return btn;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (e == null || "addText".equals(e.getActionCommand())) {
            final String newText = inTxtArea.getText();
            outTxtArea.append(newText);
            inTxtArea.setText("");
        }
    }
}

Como se puede ver es un applet normal y corriente, cabe destacar el método createGUI() donde se crean los componentes Swing del applet, y la particularidad de como le damos un nombre a cada componente para luego poder localizarlo. Esta sería la única condición que debe cumplir el applet que queremos probar.

 

5. Tests unitarios con FEST

Ya dijimos que FEST es una librería que nos ayuda a hacer tests automáticos. En el mismo proyecto donde tenemos el applet de ejemplo podemos encontrar un sencillo test:demo/applet/src/test/java/com/autentia/applet/DemoAppletTest.java

public class DemoAppletTest {

    private AppletViewer viewer;
    private FrameFixture driver;

    @BeforeClass
    public static void setUpOnce() {
        FailOnThreadViolationRepaintManager.install();
    }

    @Before
    public void setUp() throws Exception {
        viewer = AppletLauncher.applet(DemoApplet.class).start();
        driver = new FrameFixture(viewer);
        driver.show();
    }

    @After
    public void tearDown() throws Exception {
        viewer.unloadApplet();
        driver.cleanUp();
    }

    @Test
    public void given_new_applet__when_init__then_all_components_with_name_are_registered() throws Exception {
        assertThat(driver.textBox("inTxtArea"), notNullValue());
        assertThat(driver.textBox("outTxtArea"), notNullValue());
        assertThat(driver.button("btn"), notNullValue());
    }

    @Test(expected = ComponentLookupException.class)
    public void given_a_registered_applet__when_retrieve_an_InputText_with_a_wrong_name__then_throw_exception() throws Exception {
        driver.textBox("non existing");
    }

    @Test(expected = ComponentLookupException.class)
    public void given_wrong_name__when_retrieve_a_Button__then_throw_exception() throws Exception {
        driver.button("non existing");
    }

    @Test
    public void given_a_button__when_click_it__then_execute_corresponding_action() throws Exception {
        driver.button("btn").click();
        assertThat(driver.textBox("outTxtArea").text(), is("Hola Mundo!"));
    }

    @Test
    public void given_an_InputText__when_text_is_written_in_it__then_text_should_be_kept_in_the_component() throws Exception {
        driver.textBox("inTxtArea").setText("Texto cambiado!");
        driver.button("btn").click();
        assertThat(driver.textBox("outTxtArea").text(), is("Texto cambiado!"));
    }
}

Aquí vemos como en la línea 14 se crea el “driver” (la clase FrameFixture), que luego se usará para interactuar con el applet y simular su manejo. Se van localizando los componente por el nombre que les dimos al crear el applet, y una vez localizado el componente se interactúa con él.

¡OJO! Estos tests aunque son ejecutados automáticamente por JUnit, son test que se están ejecutando fuera del navegador y por lo tanto sin ninguna restricción de seguridad. Esto quiere decir que son buenos para comprobar el funcionamiento interno del applet, pero hay que tener cuidado porque una vez se ejecute dentro del sandbox del navegador y se le apliquen las restricciones de seguridad, puede que el comportamiento cambie.

Teniendo en cuenta este aviso, vamos a seguir avanzando en el tutorial para ver como si que podemos ejecutar los tests dentro de un navegador.

 

6. Tests de sistema

A estos tests los hemos denominado de sistema porque vamos a levantar todo el sistema completo para probarlos, esto es:

  • Un Servidor Web implementado con Node.js
  • Un FireFox que hará una petición al Servidor Web, y este le servirá una página con el applet que queremos probar.
  • Todo orquestado con SeleniumHQ que será el encargado de manejar el navegador y lanzar los tests contra el applet usando JavaScript y LiveConnect (LiveConnect nos permite llamar desde JavaScript a Java y viceversa).

Para que el applet que hemos desarrollado en los puntos anteriores pueda interactuar con LiveConnect no nos vale tal cual, tenemos que, de alguna manera, exponer el driver que simulará la interacción con el usuario (igual que veíamos en los tests unitarios). Para ello vamos a extender el applet que ya tenemos, de forma que toda la “parafernalia” que necesitamos para los tests estará en otra clase sin ensuciar nuestro applet original. Además esta clase como es sólo para ejecutar los tests, nunca irá al código de producción.

Para hacer esta extensión vamos a ver dos métodos:

  1. Mediante el uso de FEST.
  2. Creando nosotros nuestra propia librería para probar applets: DRIA (Driven Rich Internet Application)

La pregunta que puede surgir ahora mismo, es ¿por qué hacer nuestra propia librería si ya tenemos FEST? Podríamos decir que la documentación de FEST no es todo lo buena que nos gustaría, y además hay ciertas piezas de FEST para probar applets que han quitado en las últimas versiones. Esto unido a los cambios en la JVM en cuanto a seguridad y applets (ver URLs de referencia), hace que la integración con FEST no resulte demasiado sencilla.

Desarrollado nuestra propia librería conseguimos: sencillez (no necesitamos toda la potencia de FEST) y flexibilidad (podemos hacer lo que necesitemos).

Cualquiera de las dos opciones, FEST o DRIA, tiene ventajas e inconvenientes y dependerá de cada caso usar una y otra. Aquí vamos a ver un ejemplo con cada una de ellas para que os sirva de guía.

 

6.1. Extendiendo nuestro applet con FEST

FEST en versiones anteriores tenía una clases para probar applets pero estas parece que han desaparecido en las últimas versiones, así que vamos a hacer a mano nuestro propio adaptador.

La clase la podéis encontrar en fest/src/main/java/com/autentia/fest/AppletFixtureFactory.java

public class AppletFixtureFactory {

    private final JApplet targetApplet;
    private FrameFixture fixture;

    public AppletFixtureFactory(JApplet targetApplet) {
        this.targetApplet = targetApplet;
    }

    public FrameFixture build() {
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    fixture = Containers.frameFixtureFor(targetApplet);
                    targetApplet.setName("FEST driven " + targetApplet.getName());
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }

        return fixture;
    }
}

Lo más importante de está clase está en la línea 14 donde se crear un “driver” para el applet que se ha pasado en el constructor.

En los ficheros de demo podemos encontrar un ejemplo de uso en el fichero demo/applet-fest/src/main/java/com/autentia/applet/fest/FestDrivenDemoApplet.java

public class FestDrivenDemoApplet extends DemoApplet implements DrivenApplet {

    private FrameFixture driver;

    @Override
    public void init() {
        super.init();
        driver = new AppletFixtureFactory(this).build();
    }

    @Override
    public FrameFixture getDriver() {
        return driver;
    }

    @Override
    public void start() {
        super.start();
        driver.show();
    }

    @Override
    public void stop() {
        driver.cleanUp();
        super.stop();
    }
}

Aquí podemos destacar como en la línea 1 extendemos nuestro applet y en la línea 8 usamos la clase anterior para crear el “driver”. Este driver lo localizaremos desde JavaScript, por LiveConnect, gracias al método getDriver() de la línea 11.

 

6.2. Extendiendo nuestro applet con DRIA

El uso de la librería que hemos creado a mano (DRIA) es muy similar a lo que hemos visto en el punto anterior.

Tenemos un adaptador que podemos encontrar en la clase dria/dria-swing/src/main/java/com/autentia/dria/swing/SwingDriver.java. Esta clase es algo más larga, y sería el equivalente a la clase FrameFixture de FEST.

La clase se limita a recibir un contenedor de Swing y recorrerlo recursivamente registrando todos sus componentes por nombre.

public class SwingDriver implements RiaDriver {

    private final Map<String, TextBox> registeredInputTexts = new HashMap<String, TextBox>();
    private final Map<String, com.autentia.dria.Button> registeredButtons = new HashMap<String, com.autentia.dria.Button>();
    private int componentDeep = 0;

    public SwingDriver(Container container) {
        if (!(container instanceof DrivenApplet)) {
            throw new IllegalArgumentException("Container should implements interface " + DrivenApplet.class.getName());
        }
        registerComponentsByNameFrom(container);
    }

    private void registerComponentsByNameFrom(Container container) {
        for (Component component : container.getComponents()) {
            register(component);

            if (component instanceof Container) {
                final Container childContainer = (Container) component;
                componentDeep++;
                registerComponentsByNameFrom(childContainer);
                componentDeep--;
            }
        }
    }

    private void register(Component component) {
        final String name = component.getName();
        if (name == null) {
            return;
        }

        if (component instanceof JTextComponent) {
            addComponent(registeredInputTexts, name, new SwingTextBox((JTextComponent) component));

        } else if (component instanceof AbstractButton) {
            addComponent(registeredButtons, name, new SwingButton((AbstractButton) component));
        }
    }

    private <T> void addComponent(Map<String, T> map, String componentName, T driverToAdd) {
        if (map.containsKey(componentName)) {
            return;
        }
        map.put(componentName, driverToAdd);
    }

    private String prepareIndentation() {
        final int spacesToAdd = componentDeep * 4;
        final StringBuilder indentation = new StringBuilder(spacesToAdd);
        for (int i = 0; i < spacesToAdd; i++) {
            indentation.append(' ');
        }
        return indentation.toString();
    }

    @Override
    public TextBox textBox(String name) {
        final TextBox textBox = registeredInputTexts.get(name);
        if (textBox == null) {
            throw new NoSuchElementException("There is no InputText with name: " + name);
        }
        return textBox;
    }

    @Override
    public com.autentia.dria.Button button(String name) {
        final com.autentia.dria.Button button = registeredButtons.get(name);
        if (button == null) {
            throw new NoSuchElementException("There is no Button with name: " + name);
        }
        return button;
    }

}

Al igual que con FEST en la carpeta demo podemos encontrar un ejemplo de uso de esta clase en el ficherodemo/applet-dria/src/main/java/com/autentia/applet/dria/DriaDrivenDemoApplet.java

public class DriaDrivenDemoApplet extends DemoApplet implements DrivenApplet {

    private SwingDriver driver;

    @Override
    public void init() {
        super.init();
        driver = new SwingDriver(this);
    }

    @Override
    public SwingDriver getDriver() {
        return driver;
    }
}

Vemos que igual que antes estamos extendiendo nuestro applet, y que también exponemos el “driver” mediante el método getDriver() de la línea 12.

La ventaja en este caso es que el código queda más sencillo.

 

6.3. Interactuando con el applet mediante JavaScript gracias a LiveConnect

LiveConnect es una característica que implementan los navegadores y que nos va a permitir comunicar JavaScript con Java y viceversa. De esta forma desde un test con SeleniumHQ invocaremos un JavaScript que está en el navegador, y este JavaScript se comunicará mediante LiveConnect con el applet para simular la interacción, recoger los resultados y volver a mandarlos al test de SeleniumHQ para analizarlos.

El siguiente diagrama muestra el proceso completo.

LiveConnect
LiveConnect

Una vez entendemos el mecanismo, queda claro que necesitamos una pieza de JavaScript que hará de puente entre los tests y el applet. En este punto no he encontrado ninguna librería que funcionara (hay algo asociado con FEST, pero la verdad es que no he sido capaz de hacerlo funcionar), así que he desarrollado la mía propia.

Esta librería se encarga de generar el JavaScript de forma dinámica mientras ejecutamos los tests y luego se lo pasa a SeleniumHQ para que lo ejecute en el navegador.

Podéis encontrar el código en la carpeta dria/dria-applet/src/main/java/com/autentia/dria/applet. Y por ahora son simplemente cuatro clases, una por cada tipo de componente del applet con el que interactuamos:

  • JsAppletButton
  • JsAppletComponent
  • JsAppletDriver
  • JsAppletTextBox

Pongo el código de una a modo de ejemplo:

class JsAppletTextBox extends JsAppletComponent implements TextBox {

    public JsAppletTextBox(JavascriptExecutor jsExecutor, StringBuilder js, String name) {
        super(jsExecutor, js, "textBox", name);
    }

    @Override
    public void setText(String text) {
        js.append(".setText('").append(text).append("');");
        executeJs();
    }

    @Override
    public String text() {
        js.insert(0, "var outTxt = ");
        js.append(".text(); return outTxt;");
        return executeJs().toString();
    }

}

Se ve como tenemos un objeto js donde vamos añadiendo el JavaScript necesario para interactuar con el componente.

 

6.4. Firmado del applet

Ya tenemos todo el código listo, ahora es necesario empaquetar todas las clases del applet en un .jar y firmarlo con nuestro certificado para que el navegador lo reconozca correctamente.

Como hemos desarrollado dos formas para probar el applet FEST y DRIA, vamos a generar dos .jar distintos, de forma que podamos desplegar cada uno de forma independiente.

Vamos a ver el fichero de Gradle que se encarga de esta tarea para el applet DRIA (la parte de FEST sería análoga).

El fichero lo encontramos en demo/applet-dria/build.gradle.

dependencies {
    compile project(':demo:applet')
    compile project(':dria:dria-swing')
    testCompile "org.easytesting:fest-swing:$festSwingVersion"
}

jar {
    from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } } // Para generar un jar con todas las dependencias.
    manifest {
        attributes(
                'Application-Name': 'Testing Applet with DRIA',
                'Implementation-version': version,
                'Permissions': 'sandbox',
                'Codebase': 'localhost',
                'Application-Library-Allowable-Codebase': 'localhost',
                'Caller-Allowable-Codebase': 'localhost'
        )
    }
}

task signJar(dependsOn: jar) << {
    ant.signjar(
            jar: jar.archivePath,
            keystore: '../../certs/testing-applet.jks',
            storepass: 'autentia',
            alias: 'demoCert1'
    )
}

task copyJarToWebappSourceDir(type: Copy, dependsOn: signJar) {
    from jar.archivePath
    into '../system-test/src/main/webapp/'
}
test.dependsOn copyJarToWebappSourceDir

Cosas importantes:

  • línea 8 – generamos un sólo .jar que contendrá todas las clases, tanto nuestras, como las de librerías de terceros de las que dependemos. Así solo desplegaremos un .jar y el navegador sólo se tendrá que descargar un único.jar.
  • línea 9 – definimos los atributos del fichero META-INF/MANIFEST.MF. Son especialmente importantes:
    • Permissions – Indica si el applet se puede ejecutar en un sandbox (sería la opción más recomendable ya que al estar más restringida da menos problemas), o si necesita all-permissions.
    • Codebase – Dominio de donde se tiene que descargar el applet. Así evitamos que el applet pueda ser desplegado en otro dominio en algún intento de suplantación.
    • Application-Library-Allowable-Codebase – indica en que dominios pueden estar la página HTML que usa el applet o el fichero JNLP. Si el applet está en la misma localización que el HTML o el JNLP no haría falta especificar este atributo, pero es recomendable para evitar posibles intentos de suplantación.
    • Caller-Allowable-Codebase – Lista de dominios que pueden pueden hacer llamadas de LiveConnect. El JavaScript descargado de los dominios que están en esta lista podrán hacer llamadas al applet a través de LiveConnect.
  • línea 21 – es donde firmamos el applet indicando el almacén de certificados y el alias del certificado que queremos usar.

 

6.5. Creando el servidor Web

El siguiente paso para lanzar los tests de sistema es crear un Servidor Web que nos devuelva el HTML y los recursos, incluido el .jar de nuestro applet. Para ello vamos a usar Node.js ya que es realmente sencillo y rápido.

Para crear el servidor sólo necesitamos el fichero demo/system-test/server.js

var connect = require('connect');
connect().use(connect.static('src/main/webapp')).listen(8080);

Como véis este fichero tiene dos sencillas líneas, y lo que hace es servir por el puerto 8080 los ficheros que se encuentran en src/main/webapp.

Para arrancar el servidor basta con ejecutar en la línea de comando:

$node server.js

Y ya podremos hacer peticiones a nuestro servidor, como http://localhost:8080/system-test.html, y deberíamos ver algo como:

Pagina para la prueba del applet
Pagina para la prueba del applet

 

6.6. Ejecutando los tests de sistema

Creo que ya lo tenemos todo listo, así que el último paso es lanzar los tests. Vemos como el código de los tests de sistema:

@RunWith(Parameterized.class)
public class SystemTest {

    private static final WebDriver webDriver = new FirefoxDriver(buildTestingProfile());

    private static FirefoxProfile buildTestingProfile() {
        final FirefoxProfile profile = new FirefoxProfile();
        profile.setPreference("plugin.state.java", 2); // El 'Java Applet Plug-in' siempre activo para no necesitar 'click-to-play'.
        return profile;
    }

    @BeforeClass
    public static void setUpOnce() throws Exception {
        webDriver.get("http://localhost:8080/system-test.html");
        assertThat(webDriver.getTitle(), is("Testing Applet"));
    }

    @AfterClass
    public static void tearDownOnce() throws Exception {
        webDriver.quit();
    }

    @Parameters
    public static Collection<Object[]> data() {
        final Object[][] data = new Object[][] { { "driaApplet" }, { "festApplet" } };
        return Arrays.asList(data);
    }

    private final RiaDriver appletDriver;

    public SystemTest(String appletName) {
        appletDriver = new JsAppletDriver((JavascriptExecutor) webDriver, appletName);
    }

    @Test
    public void given_an_applet_with_text_as_input__When_click_button__Then_text_is_added_to_the_output() throws Exception {

        appletDriver.textBox("inTxtArea").setText("Desde el test de Java con Selenium");

        appletDriver.button("btn").click();

        final String outTxt = appletDriver.textBox("outTxtArea").text();
        assertThat(outTxt, is("Desde el test de Java con Selenium"));
    }
}

Cosas importantes:

  • línea 8 – estamos creando un perfil de FireFox configurando la propiedad plugin.state.java para que el FireFox no nos pregunte si queremos permitir la ejecución del applet.
    Esto no tiene nada que ver con los certificados, simplemente desde algún tiempo casi todos los navegadores tomaron la decisión de añadir este control como medida de seguridad, de forma que siempre que hay una RIA (Applet, Flash, …) piden permiso al usuario para ejecutarla. Evidentemente esto sería un inconveniente para un test automático que se tiene que ejecutar sin intervención de un operador, así que hacemos este “truco”.

    Ojo, porque esto dependerá del navegador que estemos usando para el test, en nuestro caso FireFox.

  • línea 25 – vemos como vamos a lanzar el mismo test para los dos applets: driaApplet y festApplet.
  • línea 32 – estamos usando la librería que hemos creado para generar el JavaScript y comunicarnos con el applet mediante LiveConnect. Vemos como creamos una instancia de esta librería pasándole el ejecutor de JavaScript que nos proporciona SeleniumHQ y el nombre del applet que vamos a querer probar.
  • línea 36 – es un test normal y corriente, como los que veíamos en el apartado de los test unitarios. Lo bueno de este test es justamente eso, que no sabe si está ejecutando el test contra un applet en un navegador o de forma aislada, así que podría ser reutilizable para hacer las dos cosas.

 

7. Conclusiones

Se que el tutorial ha quedado bastante denso, pero el tema no es para nada sencillo e intervienen muchas piezas distintas. De hecho el tutorial no es más que una guía para ayudaos a navegar y comprender el código que está en el repositorio, pero es el código que hay en el repositorio el que debéis estudiar para ver todos los detalles. Os animo a que os lo bajéis lo probéis, lo modifiquéis, y en general juguéis con él para hacerlo vuestro.

Además hay que tener en cuenta que, como cualquier test de sistema, es muy frágil, ya que intervienen muchas piezas distintas que tienen que estar perfectamente orquestadas. En cuanto cambie la versión de máquina virtual, el navegador, el sistema operativo, … puede ser que el test falle.

Por eso la idea de este tipo de tests no es probar absolutamente toda la funcionalidad, sino simplemente un caso de uso crítico o alguna característica concreta. En el caso del applet el test de sistema sirve para comprobar cómo se comporta ante las restricciones de seguridad impuestas por la JVM y el navegador.

Teniendo esto último en cuenta, este test podría derivar en un conjunto de tests distribuidos que se ejecutan en distintas máquinas virtuales con distintos entornos de sistema operativo, JVM y navegador, de forma que automáticamente fuéramos capaces de probar todos los entornos que consideramos críticos. Pero eso lo dejamos para otro tutorial ;P

 

8. Sobre el autor

Alejandro Pérez García, Ingeniero en Informática (especialidad de Ingeniería del Software) y Certified ScrumMaster

Socio fundador de Autentia (Desarrollo de software, Consultoría, Formación)

mailto:alejandropg@autentia.com

Autentia Real Business Solutions S.L. – “Soporte a Desarrollo”

http://www.autentia.com

Alejandro es socio fundador de Autentia y nuestro experto en Java EE, Linux y optimización de aplicaciones empresariales. Ingeniero en Informática y Certified ScrumMaster. Seguir @alejandropgarci Si te gusta lo que ves, puedes contratarle para darte ayuda con soporte experto, impartir cursos presenciales en tu empresa o para que realicemos tus proyectos como factoría (Madrid). Puedes encontrarme en Autentia: Ofrecemos servicios de soporte a desarrollo, factoría y formación.

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad