Mostrando entradas con la etiqueta pattern. Mostrar todas las entradas
Mostrando entradas con la etiqueta pattern. Mostrar todas las entradas

lunes, 27 de diciembre de 2010

Editorial de fin del año del 2010 - De vuelta a las bases

Henos aquí al final del año 2010. Primer fin de año de este blog que esperamos que sea el primero de muchos.

La verdad, en estos casi 9 meses de vida (chiste obvio al margen) nos hemos dado algunos gustos. Como dicen por ahi, justamente los gustos hay que darselos en vida y así lo hicimos.

Recuerdo cuando era chico miraba series de televisión (y aquí voy a delatar nuestra edad, ey, tengan en cuenta que la mitad de nosotros somos treintañeros todavia!! mas respeto!) como los angeles de charlie, los dukes de hazzard, BJ (la del camionero con el mono), el Sheriff Lobo (imperdible), el hombre de la Atlantida (ju juuu por DioS!), buck roger o el auto fantástico. Cuando terminaba la temporada de estas series era habitual que el ultimo capitulo fuera en realidad una selección de las mejores partes de los capítulos que se habían emitido este año. Va aquí nuestro homenaje a esta práctica, como modo de resumen del año 2010.

Hemos repasado algunos principios básicos de objetos en la-ley-de-demetrio-y-otras-yerbas-oo y en poo-ejemplos-de-la-ley-de-demeter. No quiero olvidarme tampoco de la seria sobre patrones de tests de unidad: patterns-de-tests-de-unidad-1-como-deberian-ser-los-tests-de-unidad, patrones-de-test-de-unidad-2-humble-objects o como-lograr-tests-de-unidad-rapidos.

Estos principios y patrones parecen bastante obvios y trillados(bah, así nos parecían) pero charlando con mi amigo y co-autor de Blog nos hemos dado cuenta que en estos tiempos muchas veces debido a la falta de recursos para programación o porque hay otros temas mas "fashions", lo cierto es que muchos programadores nuevos no conocen ni a estos ni a los principios más básicos de la Programación Orientada a Objetos.

Hemos recién empezado a charlar sobre un tema muy interesante, importante como buena practica pero bastante extenso como son los Code Smells en code-smells, desgranando algunos en detalle como: desconfien-de-los-parametros-booleanos, o obsesion-primitiva y comentarios

Emprendimos una batalla, desigual desde ya, tratando de aclarar cuestiones muchas veces malentendidas o mal interpretadas de las metódologías ágiles, como en con-scrum-no-alcanza o estas-realmente-haciendo-desarrollo-Agil y dos en particular que me gustaron mucho, por la repercusión que tuvieron y porque fueron generadas con discusión dentro de la comunidad ágil iberoamericana: metaforas-del-desarrollo-de-software-i y metaforas-del-desarrollo-de-software-ii. Para terminar con el articulo fundamental de como encarar el aprendizaje de cualquier nuevo conocimiento y en particular de una metodología ágil : el-camino-del-conocimiento-de-aprendiz-a-maestro.

Como niños buenos e hijos de la generación "La inmaginacion al poder", hemos discutido como debe ser un buen líder y otras cuestiones de liderazgo en liderazgo-i y liderazgo-ii. Pero por otra parte, cuestionando nuestra conciencia, nos hemos encargado de recordar las obligaciones que tenemos como verdaderos profesionales de formarnos constantemente: cuantos-libros-tecnicos-leiste-este-año, y trabajar sobre lo que tienen importancia como en mantenibilidad-vs-reusabilidad, por-que-funciona-pair-programming y nos hemos auto-retado cuando perdemos el tiempo como en larga-el-twitte o productividad-la-espartana.

Para finalizar queremos resaltar dos articulos en particular, ambos han tenido mucha difusión, y creemos saber, al menos parte de, la causa: El primero es flexibilidad-ante-el-cambio que habla sobre porque hay que estar preparados para el cambio en todo proyecto de desarrollo, sobre como descubren lo que quieren los usuarios y como el cambio es esencialmente imparable porque es sencillamente parte de la realidad. Y el otro es pasos-de-bebe-el-fin-del-debugging, una de las herramientas menos utilizadas, difundidas y comprendidas de la programación pero que nosotros le asignamos una importancia capital para enfrentar el caos de la complejidad que impone El Cambio sobre el desarrollo de software, junto, claro está con las prácticas de TDD y pair programming.

Para cerrar el balance , además de los artículos, este año organizamos dos Code Retreats, que nos permitieron compartir tiempo con otros locos (perdón, quiero decir: con otros entusiastas de estos temas) capaces de juntarse un sábado a las ocho de la mañana a programar de a pares, resolver problemas y razonar sobre como mejorar nuestro arte.

Bueno, eso es todo. Lo que hemos tratado humildemente de hacer este primer año desde este espacio es describir y promover conceptos, libros, técnicas y herramientas que consideramos poco habituales dentro del ámbito de la programación.

Creemos que la terrible presión de la ola de información junto con la inundación de nuevos lenguajes, herramientas y frameworks impone tal ritmo al programador profesional que arrasa con cuestiones de base y produce que todo se confunda en un lodazal de conocimientos superficiales y la Ultima-bala-de-plata-que-viene-a-solucionar-todos-tus-problemas.

Lo que se necesita en realidad es adquirir buenos hábitos, aplicar Buenas Practicas de desarrollo y volver a las bases.

Por eso brindamos. Salud!.





La foto del brindis es propiedad de larrazun. Algunos derechos reservados.

lunes, 28 de junio de 2010

Como lograr tests de unidad rápidos

Una de las principales características deseables en los tests de unidad de un proyecto es que sean rápidos. El típico ciclo de TDD de Red-Green-Refactor funciona mucho mejor cuando los tests se pueden ejecutar muy frecuentemente porque cuanto más tiempo dejamos pasar entre corridas de tests más dificil se hace saber que puede haber causado la falla de un test.

Ahora bien, ¿Qué tan rápidos necesitamos que sean nuestros tests? Digamos que tenemos tests que tardan en promedio 1 segundo. No parece demasiado lento, pero si hay 500 tests (un número fácil de alcanzar en un proyecto mediano) entonces la ejecución de estos tests llevaria más de 8 minutos, lo que nos impediría correr nuestros tests mas de dos o tres veces por hora.

Normalmente los tests que retrasan las corridas son aquellos que usan recursos externos, en especial las bases de datos. Michael Feathers considera que estos no son realmente tests de unidad, pero esto no quita que en el acceso a la base de datos puede haber errores y que esta funcionalidad se tiene que testear.

Una posibilidad para que los tests de unidad que tocan la base de datos no afecten al resto es armar dos test suites separadas. La test suite que no usa la BD se ejecuta dentro del ciclo normal de TDD y la otra solo en ocasiones especiales (por ejemplo antes de hacer commit en el repositorio). Este esquema es fácil de implementar, pero tiene el problema de que algunos tests de unidad se ejecutan mucho menos frecuentemente.

Otra alternativa, si uno tiene la suerte de trabajar en Ruby, es ZenTest. Esta herramienta tiene muchas utilidades para unit testing, pero la que nos interesa ahora es autotest que detecta los cambios que realizamos en el código y ejecuta solo los tests afectados por dichos cambios. También hay una herramienta parecida para Java, aunque no he tenido oportunidad de probarla. Con este tipo de herramientas ya no nos preocupa tener tests lentos, ya que solo vamos a ejecutar unos pocos por vez.

Una tercera posibilidad es extraer la funcionalidad que accede a la base de datos y esconderla detras de una Facade que funciona como un repositorio. Los tests de unidad de este repositorio acceden a la base de datos, pero los del resto de la aplicación no, ya que utilizan una implementación alternativa del repositorio que guarda los datos en memoria. De esta manera solo los tests que verifican realmente el acceso a la BD pagan la penalidad del mayor tiempo de ejecución mientras que el resto corre utilizando solo memoria. Para complementar este esquema se puede tener otra suite que ejecute todos los tests con acceso real a la base de datos. Esta suite no debería normalmente detectar errores que no detecte la suite normal, pero se la puede ejecutar como reaseguro, por ejemplo en un servidor de integración continua.

Aplicando este esquema mejoramos además el diseño ya que rompemos la dependencia entre nuestra aplicación y la DB. Los objetos de negocio dejan de tener la responsabilidad de saber persistirse y esa responsabilidad pasa al repositorio. Esta situación donde buscando que nuestra aplicación sea más testeable mejoramos su diseño es muy común, porque en general mejoras en la testabilidad se traducen en disminución del acoplamiento.

Existen otras alternativas, más a nivel de optimización de recursos, como utilizar implementaciones de bases de datos en memoria (esto es particularmente fácil con SQLite) o correr los tests en paralelo, explotando que los tests de unidad deberían ser independientes unos de otros. Con esta última idea hay que tener mucho cuidado porque dos tests que utilicen la misma BD y se ejecuten al mismo tiempo pueden fallar en forma impredecible.

Como ven hay muchas posibilidades. Lo importante es maximizar el feedback de los tests de unidad para lo cual es indispensable poder ejecutar los tests muchas veces por hora.

miércoles, 12 de mayo de 2010

Patrones de test de unidad 2 : Humble Object

Este es el segundo post de nuestra serie sobre patrones de test de unidad. Hoy vamos a ver como hacer más poderosa nuestra suite de tests de unidad extendiendo su cobertura.

En todos los proyectos hay objetos para los cuales nos parece imposible escribir tests automáticos. Muchas veces el problema es que son objetos que no podemos instanciar en un test de unidad por que están muy acoplados a la infraestructura de la aplicación.

Un ejemplo típico de esta situación es la verificación del comportamiento de un objeto que debe correr dentro de un Application Server, como por ejemplo un EJB session bean. Este objeto no se puede instanciar en un test de unidad porque los tests de unidad no corren dentro de un Application Server y por lo tanto no vamos a poder verificar su comportamiento.

La solución a este problema es extraer a un objeto plano la lógica de negocio incluida en el objeto que no podemos instanciar y verificar el comportamiento de este nuevo objeto. El objeto original se limita a manejar su relación con el contexto y delega en el nuevo objeto todo el resto de sus viejas responsabilidades.

Este patrón se conoce como Humble Object, ya que el objeto acoplado a la infraestructura pierde la lógica de negocios y se transforma en un objeto mas "humilde".

Una variante se da cuando tenemos un método que no podemos probar porque tiene efectos colaterales (por ejemplo hacer commit en la base de datos). Entonces extraemos la lógica de negocio a otro método y dejamos solo el manejo de transacciones en el método original.

La ventaja más obvia de este pattern es que vamos a tener más código cubierto por tests de unidad y por lo tanto menos bugs. Sin embargo hay otra ventaja más sutil y quizás más importante que es que estamos bajando el acoplamiento de nuestro código. Por lo tanto tendremos más posibilidades de reuso, porque como el nuevo objeto no tiene dependencias puede ser utilizado en otras partes del código. Además mejoramos la mantenibilidad, porque podemos cambiar nuestra infraestructura sin necesidad de reescribir el código de negocios.

Esta situación donde trabajamos para hacer que nuestro código sea más testable y conseguimos como efecto colateral que también sea de mayor calidad es muy común. El motivo es que en general el código díficil de testear es código de mala calidad, de manera que la dificultad para escribir tests para un objeto funciona como un indicador de la existencia de problemas de diseño.

Veamos un ejemplo simple: un programa que recibe pedidos via HTTP. Los pedidos que recibe contienen dos parámetros y el programa devuelve la suma de los mismos.

package arrogant;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import....
 
public class HttpSum {
    public static void main(String[] args) throws Exception {
        InetSocketAddress addr = new InetSocketAddress(8080);
        HttpServer server = HttpServer.create(addr, 0);
 
        server.createContext("/", new HttpSumHandler());
        server.setExecutor(Executors.newCachedThreadPool());
        server.start();
    }
 
}
 
class HttpSumHandler implements HttpHandler {
 
 
    public void handle(HttpExchange exchange) throws IOException {
        String result = calculateSum(exchange);
        sendResponse(exchange,result);
    }
 
    private String calculateSum(HttpExchange exchange) {
        int a = Integer.parseInt(getHeader(exchange, "a"));
        int b = Integer.parseInt(getHeader(exchange, "b"));
        return new Integer(a + b).toString() ;
    }
 
    public String getHeader(HttpExchange exchange,String name) {
        Headers requestHeaders = exchange.getRequestHeaders();
        return requestHeaders.get(name).get(0).toString();
    }
 
 
    private void sendResponse(HttpExchange exchange, String result) throws IOException {
        Headers responseHeaders = exchange.getResponseHeaders();
        responseHeaders.set("Content-Type", "text/plain");
        exchange.sendResponseHeaders(200,result.getBytes().length );
        OutputStream responseBody = exchange.getResponseBody();
        responseBody.write(result.getBytes());
        responseBody.close();
    }
}

El código que podríamos llamar "de negocio" se encuentra en el método calculateSum, pero no podemos testearlo porque está totalmente mezclado con el código que se ocupa de la interfaz HTTP.

Un primer intento para extraer la funcionalidad a testear es el siguiente:

package humble1;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.util.concurrent.Executors;
import...
 
 
public class HttpSum {
    public static void main(String[] args) throws Exception {
        InetSocketAddress addr = new InetSocketAddress(8081);
        HttpServer server = HttpServer.create(addr, 0);
 
        server.createContext("/", new HttpSumHandler());
        server.setExecutor(Executors.newCachedThreadPool());
        server.start();
    }
 
}
 
class HttpSumHandler implements HttpHandler {
 
    public void handle(HttpExchange exchange) throws IOException {
 
        String result = calculateSum(exchange);
 
        sendResponse(exchange,result);
 
    }
 
    private String calculateSum(HttpExchange exchange) {
        int a = Integer.parseInt(getHeader(exchange, "a"));
        int b = Integer.parseInt(getHeader(exchange, "b"));
        Adder adder = new Adder(a,b);
        return new Integer(adder.sum()).toString();
    }
 
    public String getHeader(HttpExchange exchange,String name) {
        Headers requestHeaders = exchange.getRequestHeaders();
        return requestHeaders.get(name).get(0).toString();
    }
 
 
    private void sendResponse(HttpExchange exchange, String result) throws IOException {
        Headers responseHeaders = exchange.getResponseHeaders();
        responseHeaders.set("Content-Type", "text/plain");
        exchange.sendResponseHeaders(200,result.getBytes().length );
        OutputStream responseBody = exchange.getResponseBody();
        responseBody.write(result.getBytes());
        responseBody.close();
    }
}
 
class Adder {
    int a;
    int b;
 
    public Adder(int a, int b) {
        this.a = a;
        this.b = b;
    }
 
    public int sum() {
        return a+b;
    }
 
}

Aquí la lógica de negocio se extrae a una clase Nueva (Adder) que recibe todos los parámetros que necesita en su constructor. La clase Adder no tiene dependencias y puede ser fácilmente testeada.

Sin embargo este enfoque puede no ser adecuado cuando se reciben muchos parámetros del contexto, ya que en esos caso la construcción del objeto que se ocupa de la lógica de negocios es demasiado compleja. En esta situación puede ser mejor la siguiente idea:

 
package humble2;
 
 
 
import java.io.IOException; 
 
import java.io.OutputStream;
 
import java.net.InetSocketAddress; 
 
import java.util.concurrent.Executors; 
 
 
 
import ... 
 
 
 
public class HttpSum {
 
   public static void main(String[] args) throws Exception {
 
   InetSocketAddress addr = new InetSocketAddress(8082);
 
   HttpServer server = HttpServer.create(addr, 0); 
 
   server.createContext("/", new HttpSumHandler());
 
   server.setExecutor(Executors.newCachedThreadPool());
 
    server.start();
 
  }
 
}
 
 
 
  class HttpSumHandler implements HttpHandler {
 
        public void handle(HttpExchange exchange) throws IOException {
 
         String result = calculateSum(exchange);
 
         sendResponse(exchange,result);
 
    }
 
 
 
    private String calculateSum(HttpExchange exchange) {
 
         IAdderInput input = new HttpAdderInput(exchange);
 
         Adder adder = new Adder(input);
 
        return new Integer(adder.sum()).toString() ;
 
    }
 
 
 
    private void sendResponse(HttpExchange exchange, String result) throws IOException {
 
 
 
        Headers responseHeaders = exchange.getResponseHeaders();
 
        responseHeaders.set("Content-Type", "text/plain");
 
        exchange.sendResponseHeaders(200,result.getBytes().length );
 
        OutputStream responseBody = exchange.getResponseBody();
 
        responseBody.write(result.getBytes());
 
        responseBody.close();
 
    }
 
 }
 
 
 
   interface IAdderInput {
 
         int getA();
 
         int getB();
 
     }
 
 
 
    class HttpAdderInput implements IAdderInput {
 
        HttpExchange exchange;
 
        public HttpAdderInput(HttpExchange exchange) {
 
        this.exchange = exchange;
 
   }
 
 
 
   @Override public int getA() {
 
             return getIntegerParameter("a");
 
    }
 
 
 
   @Override public int getB() {
 
            return getIntegerParameter("b");
 
    }
 
 
 
   private int getIntegerParameter(String name) {
 
             return Integer.parseInt(getHeader(name));
 
 
 
   }
 
  private String getHeader(String name) {
 
             Headers requestHeaders = exchange.getRequestHeaders();
 
             return requestHeaders.get(name).get(0).toString();
 
         }
 
   }
 
 
 
 
 
  class Adder {
 
         IAdderInput input;
 
 
 
        public Adder(IAdderInput input) {
 
             this.input = input;
 
        } 
 
 
 
        public int sum() {
 
               return input.getA() + input.getB();
 
          }
 
  }

En este caso cuando se crea el objeto de negocios este recibe como parámetro una instancia de una interfaz que representa todos los parámetros que se toman del contexto. En el ambiente de ejecución, recibe una implementación que es un Wrapper sobre el código HTTP original y que se ocupa de la extracción de los parámetros necesarios. En ambiente de tests de unidad, seguramente recibirá un Mock de la interfaz que devolverá los valores que nos interesen para los tests.