martes, 27 de marzo de 2012

Integración Continua

Hablar del concepto de integración continua sin mencionar en el primer parrafo a Martin Fowler es un poco presuntuoso como poco, Martin ha sido durante años el campeón, como el mismo dice, de la integración continua.

La integración continua consiste en la compilación de los módulos de un proyecto, y la ejecución de las pruebas de dichos módulos de una forma frecuente. 


Compilación


Realizar la compilación de los módulos de un proyecto es algo que podemos hacer manualmente o bien utilizando nuestro entorno de desarrollo favorito, o bien utilizando herramientas como Ant o Maven. 


Ejecución de pruebas


En los post anteriores hemos visto como realizar pruebas de código mediante el empleo de un framework bastante popular como es junit. Vímos también como utilizar Ant para la ejecución de estas pruebas.


Introducción


En proyectos pequeños sabemos realizar tanto la compilación, en los entornos de desarrollo podemos hacerlo automáticamente con cada cambio, como las pruebas.


En proyectos medianos, se complica la situación cuando hay varios módulos y varios desarrolladores trabajando en ellos, ya que la integración entre ellos se complica, y sobretodo los cambios en el código pueden hacer que todo el proyecto deje de cumplir adecuadamente los requisitos. 


Por esto, el planteamiento de la integración continua. Un entorno en el que mediante determinadas mejores prácticas, metodologías y herramientas se llega a solventar la problemática de la integración de los módulos de software, a veces conocida como el infierno de la integración.


Metodologías


No hay metodologías per se que se desarrollen para el concepto de integración continua, sin embargo si hay ciertas metodologías de desarrollo que cohabitan con el concepto de integración continua como la programación extrema, así como desarrollo ágil, y más recientemente scrum.


La metodología de gestión de proyectos waterfall no se adapta bien al concepto de integración continua, siendo la integración continua en el proceso de desarrollo.


Mejores Prácticas


  • Mantener un repositorio de código.
  • Automatizar la construcción.
  • Automatizar las pruebas.
  • Realizar commit al repositorio diariamente.
  • El proyecto se debe poder construir con cada commit, el desarrollador debe ejecutar pruebas en su entorno local antes de hacer el commit.
  • La construcción del proyecto debe ser rápida.
  • Realizar las pruebas en una copia del entorno de producción.
  • Debe ser sencillo obtener los últimos entregables.
  • Cualquiera puede ver los resultados de la última construcción.
  • Automatiza la instalación (deployment).

Herramientas


Existe una amplia variedad de herramientas que nos van a ayudar en la implementación de estas mejores prácticas.
  • Repositorios de código: cvs, svn y git.
  • Gestión de proyecto: maven2.
  • Automatización de la construcción: ant, maven2.
  • Automatización de las pruebas: junit, dbunit, xunit, testng.
  • Automatización de la integración continua: cruisecontrol, hudson.

martes, 20 de marzo de 2012

Pruebas unitarias con junit (II)

En el post anterior me quedé un poco corto, junit nos permite muchas más cosas. Expliqué que las pruebas unitarias deben ser independientes, ahora veremos como podemos hacer esto.

En el caso que nos ocupaba, cada prueba era autocontenida, los datos que se utilizaban formaban parte de la clase, más aún, del método de prueba. Vemos que los operadores de la operación de suma están contenidos en el código del método.

public void testAdd100 (){
int num1 = 3;
int num2 = 2;
int total = 5;
int sum = 0;

sum = Mathematica.add(num1, num2);
assertEquals(sum, total);
}

En otras ocasiones nos enfrentaremos con clases que tenemos que inicializar, bases de datos que deben estar en un determinado estado antes de realizar la prueba, y que deben estar en un determinado estado al terminar la prueba (o pruebas).

Junit nos provee con dos métodos que podemos sobreescribir

setup: se ejecuta al principio de la ejecución de la clase de pruebas, en el podemos inicializar clases, abrir conexiones, insertar datos, cargar ficheros, ...

teardown: se ejecuta después de ejecutar el último test de la clase de pruebas, en el podemos restaurar los valores según se hayan definido para el fin de la prueba.

Vamos a probar una clase que necesita configuración inicial:

public class Configuracion {

private int rowNumber;

public Configuracion (int valor){
this.rowNumber=valor;        
}

public int getRowNumber (){
return  this.rowNumber;
}

public void setRowNumber ( int valor ){
this.rowNumber=valor;
}
}


Definimos dos casos de prueba

En el primero crearemos un valor para el rowNumber, y validaremos que este valor está correctamente establecido.

En el segundo estableceremos un valor para el rowNumber, y validaremos que este valor está correctamente establecido.

Creamos la clase de prueba

public class TestConfiguracion extends TestCase{

protected Configuracion configuracion;

public void setUp (){
configuracion = new Configuracion (5);
}

public void testConfiguracion001 () {
int c= configuracion.getRowNumber();
int inicial = 5;

assertEquals(c,inicial);
}

public void testConfiguracion002 () {
int valor = 3;
configuracion.setRowNumber(valor);
int c= configuracion.getRowNumber();

assertEquals(c,valor);
}
}

Para crear la clase configuración hemos de asignar un valor, algo que hacemos en el método setUp(). Así para el primer test está establecido con el valor que fijamos en la llamada al constructor.

Algo que no mencioné, los métodos de prueba se ejecutan en orden alfabético.

En la segunda prueba, nos aseguramos que el método setRowNumber funciona adecuadamente, ya que en la anterior hemos validado que tanto el constructor como getRowNumber funcionan adecuadamente.

¿Cómo se entiende que en un blog en español haya escrito la clase que vamos a probar en ingles? Sólo para remarcar una idea, he partido de unos requerimientos y mi parte inglesa ha escrito la función que los cumple, y mi parte castellana la función que los valida. Algo bastante habitual en grandes proyectos.

Para ejecutar esta clase de test he creado un target para ant de la siguiente manera



                 








Teniendo el resultado

testConfig:

[junit] Running com.blog.TestConfiguracion
[junit] Tests run: 2, Failures: 0, Errors: 0, Time elapsed: 0,016 sec

sábado, 17 de marzo de 2012

Vamos hacia delante

Habitualmente 3 años despues de una versión principal, 1 año despues de la siguiente versión principal, 6 meses despues de que la subsiguiente versión es tomada como JRE por defecto en java.com una versión mayor de Java llega al final de su vida.

Despues de empezar por el tejado, lleguemos a las paredes. Java 6 va a llegar al final de su vida el 6 de Noviembre de 2012. Lo que implica todo el esfuerzo de migración, si fuera necesaria, hacia versiones posteriores y por otro lado la mejora, aunque en ocasiones se ve más bien como "mejora", las nuevas características de la versión 7 y la versión 8.

Un interesante artículo (en ingles) sobre la llegada al final de la vida de una versión de Java para aquellos que no tienen muy claro lo que esto significa.

jueves, 15 de marzo de 2012

Hay vida después de la adquisición

Es cierto que un alto porcentaje de las adquisiciones, al final tienen la pinta de que se compra la compañía xyz porque “molesta” y se cierra y listos. Otras al final no llevan a donde se esperaba, y se acaba revendiendo lo que se compró por un precio muy inferior al inicial, y acaba todo de una forma un tanto agridulce.

Ultimamente he leido un par de noticias que son algo más alentadoras, ya que al principio quizá tenía más pinta de pez grande se come a pez pequeño, que de otra cosa.

Hablo de la última versión de Mysql Cluster 7.2 y Java Server Faces.

En el caso de Mysql, parecía que la gran compañía de bases de datos compraba a la compañía que más pegaba últimamente en el opensource, y se auguraba un futuro bastante negro. Pero ahí estamos con una última versión mejorada, y más rápida. Veremos como seguimos por aqui.

En el case de JavaServer Faces, el advenimiento de html5 y que adobe ha dejado de apostar, en cierta medida, por flash/flex/as (llamemoslo x) nos hace también ser positivos.

martes, 13 de marzo de 2012

Pruebas unitarias con junit

Si has oído hablar de pruebas de software, has oído hablar de junit, incluso de alguno de sus sucesores, xunit, etc.

Junit es un framework de pruebas, es decir toda la parafernalia que vas a necesitar poner en tu código java para poder hacer pruebas unitarias de software.

Una prueba unitaria es la prueba de un módulo de software, como puedes adivinar cuantos más módulos tenga una aplicación, más pruebas tendremos que realizar. Si las pruebas las podemos automatizar de alguna manera las podremos repetir tantas veces como sea necesario y dedicar nuestro tiempo a revisar los informes de pruebas y retocar la aplicación, más que a probar y probar, generar los informes, revisar los informes y retocar la aplicación.

Acabo de describir el proceso de integración continua, descargar del sistema de control de versiones el fuente de la aplicación, compilarlo, y realizar las pruebas unitarias y de integración de los módulos. Al ir probándose el código cada cierto tiempo, no nos encontraremos con las sorpresas de último momento.

Volviendo a las pruebas unitarias, ¿cuáles son las características de una prueba unitaria?

  • Independiente: una prueba no debe afectar a las otras.
  • Completa: deben cumplir la mayor parte, o la totalidad, del código.
  • Repetible: una prueba tiene que poderse ejecutar tantas veces sea necesario.
  • Automatizable: una prueba debe ejecutarse sin intervención manual.
  • Profesional: una prueba no deja de ser código, por lo que deben estar documentadas, y bien diseñadas.

Existen varios frameworks de pruebas, testng, junit, y otros, aunque ahora veremos como utilizar junit.

Primero tenemos que tener claro que porción de código queremos probar, en nuestro caso será esta nueva clase que estoy desarrollando:

public class Mathematica {

public static int add (int a, int b){
return a+b;
}

}

Los requerimientos, que tenemos que tener en cuenta tambien en la fase de pruebas unitarias, nos dicen que la funcion add aplicada sobre dos números enteros nos devolverá la suma aritmética de ambos.

Por ello nos disponemos a crear nuestros casos de prueba, vamos a crear 4 casos:

  1. 3+2 = 5
  2. 3+0 = 3
  3. 0+2 = 2
  4. 3+2 != 4

Creamos el típico caso de “happy path”, o que seguro sabemos que podemos cumplir. Muchos programadores nos quedamos aquí, pero tenemos que probar tambien ciertos límites como la suma con cero, y que el valor que obtenemos es diferente de cualquier otro.

Para estas pruebas básicas usamos dos funciones del framework junit:

  • assertEquals(x,y) que compara dos valores x e y (está implementado para casi todos los tipos de datos.
  • assertFalse(z) que verifique un valor booleano.

Tenemos que crear una clase que extienda la clase del framework TestCase:

public class TestMathematica extends TestCase

Y dentro aquellos métodos de prueba correspondientes a los casos de prueba, estos métodos de prueba comienzan con la palabra test.

Por ejemplo, para el primer caso de prueba:

public void testAdd100 (){
int num1 = 3;
int num2 = 2;
int total = 5;
int sum = 0;
sum = Mathematica.add(num1, num2);

assertEquals(sum, total);
}


Para realizar la ejecución de los casos de prueba hay varias formas, con eclipse, netbeans, maven, y ant, por poner algunos ejemplos. Por ejemplo veamos como hacerlo con ant



                 






Al ejecutar la clase TestMathematica resultará:

testMath:
[junit] Running com.blog.TestMathematica
[junit] Tests run: 4, Failures: 0, Errors: 0, Time elapsed: 0 sec

El código completo de la clase de pruebas es:

public class TestMathematica extends TestCase {

public void testAdd100 (){
int num1 = 3;
int num2 = 2;
int total = 5;
int sum = 0;
sum = Mathematica.add(num1, num2);

assertEquals(sum, total);
}

public void testAdd200 (){
int num1 = 3;
int num2 = 0;
int total = 3;
int sum = 0;
sum = Mathematica.add(num1, num2);
assertEquals(sum, total);
}

public void testAdd300 (){
int num1 = 0;
int num2 = 2;
int total = 2;
int sum = 0;
sum = Mathematica.add(num1, num2);
assertEquals(sum, total);
}

public void testAdd400 (){
int num1 = 3;
int num2 = 2;
int total = 4;
int sum = 0;
sum = Mathematica.add(num1, num2);
boolean condition = sum==total;
assertFalse(condition);
}

}

jueves, 8 de marzo de 2012

Cifrado datos

Una de las cuestiones importantes hoy en día son los diferentes aspectos de la seguridad, cifrado de datos, firmas digitales, etc. En un sólo post me parece mucho recorrer todos los aspectos, por tanto, para ir abriendo boca empezaremos con un simple cifrador que podemos usar para proteger de miradas indiscretas aquella información de nuestra preferencia.

Primero tenemos que definir una clave que usaremos para cifrar los datos:

            byte[] iv = new byte[]{
                    (byte)0x8E, 0x12, 0x39, (byte)0x9C,
                    0x6F, 0x6F, 0x6F, 0x5A
            };

Después tendremos que crear dos objetos de cifrado, uno para cifrar y otro para descifrar:

            Cipher ecipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
            Cipher dcipher = Cipher.getInstance("DES/CBC/PKCS5Padding");


Después de inicializarlos sólo tenemos que copiar un stream de entrada en otro de salida de la siguiente forma:

a) Para cifrar:

        out = new CipherOutputStream(out, cipher);
        copyStreams(in,out);

b) Para descifrar:

        in = new CipherInputStream(in, cipher);
        copyStreams(in,out);

Bueno, el código completo es el siguiente:

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.spec.AlgorithmParameterSpec;
import java.util.logging.Logger;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

public class Encrypt {

    private final static Logger myLogger= Logger.getLogger( Encrypt.class.getName() );
    private static int tamBuffer=2048;

    protected static void copyStreams (InputStream in, OutputStream out){
        byte[] buffer = new byte[tamBuffer];
        
        try {
            // Leer del stream de entrada, y escribir en el de salida usando el buffer.
            int numLeidos = 0;
            while ((numLeidos = in.read(buffer)) >= 0) {
                out.write(buffer, 0, numLeidos);
            }
            out.close();
        } catch (java.io.IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void encrypt(Cipher cipher, InputStream in, OutputStream out) {
        myLogger.info("Iniciando encriptado de datos...");
        out = new CipherOutputStream(out, cipher);
        copyStreams(in,out);
        myLogger.info("Finalizado encriptado de datos...");
    }

    public static void decrypt(Cipher cipher, InputStream in, OutputStream out) {
        myLogger.info("Iniciando desencriptado de datos...");
        in = new CipherInputStream(in, cipher);
        copyStreams(in,out);
        myLogger.info("Finalizado desencriptado de datos..."); 
    }
    

    public static void main(String[] args) {

        String ficheroACodificar="fichTxt.txt";
        String ficheroCodificado="fichCod.cod";
        String ficheroOriginal="fichOrig.txt";

        //Creamos las claves asimétricas
        try{

            SecretKey key = KeyGenerator.getInstance("DES").generateKey();

            //Creo una clave para la generación de los Cipher
            byte[] iv = new byte[]{
                    (byte)0x8E, 0x12, 0x39, (byte)0x9C,
                    0x6F, 0x6F, 0x6F, 0x5A
            };

            AlgorithmParameterSpec paramSpec = new IvParameterSpec(iv);

            Cipher ecipher = Cipher.getInstance("DES/CBC/PKCS5Padding");
            Cipher dcipher = Cipher.getInstance("DES/CBC/PKCS5Padding");

            // Creamos los dos cifradores, para cifrar y para descifrar.
            ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
            dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);

            encrypt(ecipher, 
                    new FileInputStream(new File(ficheroACodificar)), 
                    new FileOutputStream(new File( ficheroCodificado)));
            
            decrypt(dcipher,
                    new FileInputStream(new File(ficheroCodificado)),
                    new FileOutputStream(new File(ficheroOriginal)));
            
        }
        catch (Exception e){
            e.printStackTrace();
        }
    }

}

martes, 6 de marzo de 2012

Tecnología de log de java

Explicaré como usar el API de logging que viene por incluido en el entorno de desarrollo de Java.

Como indicaba el entorno de desarrollo, JDK, contiene el API de logging de java. A través del logger se puede enviar el texto que queremos anotar o bien a un determinado fichero, a la consola, etc.. El API de logging permite configurar la manera en que estos mensajes son anotados, se pueden distinguir distintas categorías o prioridades (severe, warning, info, config, fine, finer, finest), se pueden formatear de distintas formas, sobre que clase(s) queremos anotar información, etc...

Los pasos a seguir son

a) Crear un Logger.
b) Seleccionar el nivel de anotación.
c) Seleccionar el Manejador.
d) Formato de los mensajes.

a) Crear un logger

Crear un logger es sencillo, ya que solamente tenemos que utilizar la clase Logger del paquete java.util.logging de la siguiente manera:

import java.util.logging.Logger;

private final static Logger MyLogger= Logger.getLogger( MyClass.class.getName() );

b) Nivel de anotación

Tenemos los siguientes niveles de anotación, de menor a mayor importancia:

FINEST
FINER
FINE
CONFIG
INFO
WARNING
SEVERE

Con esto podemos decidir con qué nivel de importancia damos a los mensajes que queremos
anotar. El funcionamiento de las anotaciones es entonces el siguiente:

i) En tiempo de desarrollo determinamos la importancia de las anotaciones que queremos sacar, o bien por consola o bien por fichero.
ii) Durante la ejecución se puede gestionar el nivel mínimo que vamos a querer anotar. A partir de el nivel seleccionado todos los mensajes serán anotados.

Además de los niveles mencionados se puede establecer el nivel ALL, que anota todo, y OFF, que no anotará nada.

c) Selección del Manejador

Una vez creado el logger, podemos asignar tantos manejadores (handlers) como necesitemos para nuestros propósitos.

Así pues, cada manejador (handler) recibe los mensajes que debe anotar y los muestra en el destino que hayamos elegido.

Por defecto existen dos manejadores:

i) Consola: escribe el mensaje en la consola. ConsoleHandler.
ii) Fichero: escribe el mensaje en un fichero. FileHandler.


d) Formato de los mensajes

El formato de los mensajes a anotar, se aplica mediante el uso de un formateador a la salida de cada handler.

Hay dos clases que nos ayudan a dar formato a los mensajes:

i) SimpleFormatter: nos ayuda a generar mensajes de log como texto.
ii) XMLFormatter: nos ayuda a generar mensajes de log en formato XML.

Estas dos clases se pueden extender y generar nuestros propios formateadores.

Yendo directamente a ver un ejemplo de código:

import java.io.IOException;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;

public class TestingLogger {

private final static Logger myLogger= Logger.getLogger( TestingLogger.class.getName() );

public static void main(String[] args) throws SecurityException, IOException {
myLogger.setLevel(Level.INFO);

// Crear un manejador de dispositvo de logging.
FileHandler fileTxt = new FileHandler("Anotaciones.txt");

// Crear un formateador de tipo SimpleFormatter
SimpleFormatter formatterTxt = new SimpleFormatter();
fileTxt.setFormatter(formatterTxt);
myLogger.addHandler(fileTxt);

myLogger.severe("Anotacion Severe");
myLogger.warning("Anotacion Warning");
myLogger.info("Anotacion Info");
myLogger.finest("Anotacion Finest");

myLogger.info("------------ Cambio de Nivel ------");

myLogger.setLevel(Level.SEVERE);

myLogger.severe("Anotacion Severe");
myLogger.warning("Anotacion Warning");
myLogger.info("Anotacion Info");
myLogger.finest("Anotacion Finest");
}

}



Y la correspondiente salida obtenida:

01-ene-2012 13:43:54 com.blog.TestingLogger main
GRAVE: Anotacion Severe
01-ene-2012 13:43:54 com.blog.TestingLogger main
ADVERTENCIA: Anotacion Warning
01-ene-2012 13:43:54 com.blog.TestingLogger main
INFO: Anotacion Info
01-ene-2012 13:43:54 com.blog.TestingLogger main
INFO: ------------ Cambio de Nivel ------
01-ene-2012 13:43:54 com.blog.TestingLogger main
GRAVE: Anotacion Severe

Observamos como afecta el cambio de nivel de logging en los mensajes obtenidos en el fichero Anotaciones.txt