Mostrando entradas con la etiqueta tests de unidad. Mostrar todas las entradas
Mostrando entradas con la etiqueta tests de unidad. Mostrar todas las entradas

miércoles, 29 de septiembre de 2010

Toda la verdad acerca de los Tests de Unidad

Porque son tan importantes los tests de unidad?

Uno podria preguntarse que hay detrás de taaaanto rollo acerca de una de las prácticas fundamentales tanto de XP como de toda lista de Buenas Prácticas de Programación. Porque es tan importante hacer tests de unidad?. Este posteo intenta justamente responder a esa pregunta, explicando detalladamente los beneficios principales que se obtienen al hacer tests de unidad.

Esta lista no intenta ser extensiva, ni se obtienen solo al hacer Test Driven Development, TDD, ya que algunos de los beneficios se obtienen también haciendo tests de unidad "PostMorten", es decir, luego de escribir el código.

Vamos, primero, a la enciclopedia galactica:

Tests de unidad: son los tests que un programador debe escribir para testear que su código hace lo que él entendió que se le solicitó que haga, a través del documento de especificación funcional, sea una historia de usuario, caso de uso o conversación con usuarios.

Hacemos esta distinción para diferenciarlos de los tests de aceptación que son aquellos tests de más alto nivel que deben verificar que la funcionalidad desarrollada es aceptada de acuerdo a lo definido en el relevamiento con los usuarios.

Por otro lado cuando decimos que se hacen tests de unidad o TDD estamos asumiendo que se realizan correctamente y con un cubrimiento del total de código del modelo (en un framework MVC) escrito de más del 80%.

Para aquellos que quieran hacer el salto más significativo en el desarrollo de software desde la invención de la Programación Orientada a Objetos (y no estamos exagerando), los beneficios son:

El beneficio global primero y quizás más importante es que el conjunto de tests de unidad funcionan como una malla de seguridad que permite hacer cambios al código de una manera segura y órdenes de magnitud más rápida que cuando no tenemos tests. Y esto para un mundo no perfecto como el que vivimos en el cual los usuarios/la realidad/Lo-que-Sea piden cambios de funcionalidad constantemente no es poca cosa.

En detalle, los beneficios que nos proveen los tests de unidad son incontables (como los beneficios de la soja ;) :

  1. Los tests de unidad verifican que el código en realidad funciona (de acuerdo a lo que entendió el programador, vale la pena aclararlo) lo cual significa que vamos a tener menos bugs y errores.
  2. TDD fuerza a pensar en los contratos de clases y métodos antes de escribir el código, lo cual lleva a un diseño mejor y mucho más preciso de la solución al problema planteado.
  3. Los tests de unidad hacen posible mejorar iterativamente el diseño del sistema sin romperlo. Como contamos con un conjunto de tests de unidad que verifican todo lo que se puede romper, podemos aplicar mejoras al diseño de manera incremental a través de pequeños pasos de refactoring y luego verificar si funciona corriendo todos los tests. Ante un error podemos fácilmente determinar que parte del nuevo código rompió el sistema y corregirlo.
  4. No reemplazan pero complementan muy bien a los tests de aceptación y de integración.
  5. En si sirven como un conjunto de tests de regresión de bajo nivel.
  6. Funcionan como documentación y Reduce el tiempo necesario para entender el código escrito. En general nos olvidamos muy rápidamente de lo que hace el código que escribimos hace apenas unos días atrás (mas de una vez ha pasado que miramos el código asombrados pensando en el... espécimen que habrá escrito semejante zafarrancho para terminar dándonos cuenta que fuimos nosotros mismos.... y no, no digan que nunca les paso). Imaginen además cuando tenemos que entender código escrito por otros. Los tests de unidad funcionan como perfectos ejemplos de utilización del código, documentan la utilización correcta del cada método no trivial y de la relación entre las clases. Y a diferencia de la documentación no quedan desactualizados al instante porque en cuanto eso sucede uno o varios del 100% de los tests de unidad comienzan a fallar y debemos arreglarlo (o el test o el código).
  7. Reduce el costo de mantenimiento. En Lean Software Development se define el costo de un bug como el tipo de criticidad del error (bajo, medio, alto), multiplicado por el tiempo en que permanece no detectado. Tener un buen conjunto de tests de unidad permite reducir drásticamente el tiempo que lleva detectar un bug, y corregirlo. A la vez, si uno desarrolla el sistema haciendo TDD se introducirán muchos menos errores y además cuando se sucede se detectan casi inmediatamente, el error esta en lo ultimo que se cambió.
  8. Nos sacan de un viejo círculo vicioso del desarrollo de software: como tenemos miedo de realizar cambios en la estructura de un programa, hacemos las modificaciones imprescindibles mediante parches, lo que causa que el programa sea mas díficil de modificar, lo que causa que nos de miedo realizar cambios estructurales...
  9. TDD nos fuerza a a usar Pasos de Bebé y por lo tanto a pasar menos tiempo en el debugger.

Una de las objeciones principales que se hacen en contra de escribir tests de unidad es que como es más cantidad de código que hay que escribir entonces esto va a hacer que el desarrollo vaya más lento, verdad?. Esto sería verdad si fuera que nosotros, los programadores, somos una especie de taquígrafos que nos pasamos la mayor parte del tiempo tipeando en un teclado, y entonces la cantidad de teclas por minuto se contaría como productividad. Pero esto no es asi, nos pagan por pensar, por diseñar y evolucionar una solución a un dominio de problemas. Esta comprobado que la mayor parte del tiempo un programador la pasa pensando como modelar una solución y, sobre todo, buscando errores y bugs. Escribir tests de unidad sirve justamente para hacer estas dos actividades mucho mas eficientes.

Como conclusión, y aunque sea antiintuitivo, escribir tests de unidad lleva menos tiempo total que no escribirlos.

lunes, 13 de septiembre de 2010

2do Code Retreat

El Evento

El viernes 10/09 realizamos el Segundo Code Retreat en Buenos Aires, en las instalaciones del MUG (Microsoft User Group).

Queremos antes que nada agradecer a los que nos ayudaron a organizar este Retreat, particularmente a Juan Gabardini, referente de la comunidad ágil de Argentina (recomendamos su blog softwareagil.blogspot.com), a Martin Salias (de Southworks ), a Carlos Peix y especialmente a Oscar Turquet del MUG.

La experiencia fue muy satisfactoria, y en esta oportunidad tuvimos el aporte de la vision de gente con mucha experiencia como Carlos Peix, Martin Alaimo (de Kleer) o como Juan (Gabardini) que proviene del sector de Testing.

La lista de Asistentes al Retreat fue :

  • Carlos Meschini
  • Carlos Peix
  • Martin Alaimo
  • Matias Blanch
  • Carlos Pantelides
  • Fernando Claverino
  • Nicolas Bases
  • Juan Gabardini
  • Victor Jorge Paredes
  • Gonzalo Amestoy
  • Jose Vidal

Conclusiones del Retreat

En general la mecanica del Retreat funcionó, encarando el problema a resolver cada vez desde cero, borrando el codigo entre iteraciones y cambiando de compañero de Pair Programming. Falto quizás una iteración más para hacer mas aceitada la mecánica pero la hora y el día en particular no daba para mucho mas.

El tema de hacerlo un viernes ayuda y complica a la vez, porque hay gente que no puede salir de sus trabajos y por otro lado esta el cansacio de la semana, pero en general hubo muy buena onda y energia como para completar 3 iteraciones completas, con una retrospectiva final que se extendio por casi 40 minutos.

Uno de los puntos en que coincidieron todos los participantes, tratando de hacer TDD, es la dificultad para incrementar la funcionalidad de los tests de a pequeños pasos. En general los dos o tres primeros tests "triviales" salen mas o menos bien, simples y en pequeños pasos, pero cuando hay que avanzar en un test que permita introducir funcionalidad no-trivial en general se escribe un test con un salto demasiado grande lo cual dificulta luego el desarrollo.

Justamente otro de los temas que salieron fue la relación entre facilidad de testing y buen diseño del código. Es decir, si se dificulta testear algo muchas veces es porque hay problemas con el diseño del modelo de la solución.

A veces cuesta mantener los tests de unidad porque no prestamos la suficiente atención a la calidad del código de los mismos, ya que es código fuente se deben tener todos los cuidados y aplicar todos los principios y buenas prácticas como con el código de la aplicación principal, sino su mantenimiento se convierte en una pesadilla. Por ejemplo, si un tests prueba más o menos lo mismo que otro se deben eliminar porque la duplicación es uno de los Code Smells que más problema trae a la mantenibilidad del código.

Entre las cuestiones "negativas" que de alguna manera son causadas por la dinámica del Retreat se mencionó que la presión de avanzar en la solución del problema muchas veces hacia que uno le prestara poca atención a la etapa de Refactoring.

En TDD, despues de hacer el test, y de hacer lo necesario para pasar, cuando los tests funcionan (La barra esta toda en "verde") se debe evaluar el diseño del código para ver si es necesario refactorizar. Este punto quedaba de lado muchas veces causados por la "presión" de la duración de la iteración.

Otro punto "flojo" es que en general no se hizo Pair Programming respetando tajantemente los roles de "Conductor" y "Navegante". Es decir estaban ambos integrantes muy compenetrados en los detalles más chicos del código y el que no tecleaba no funcionaba como "navegante", mirando todo desde un punto de vista más alejado, previniendo de posibles desviaciones (como fue esta de no evaluar si correspondia aplicar Refactoring).

Ademas de C# y Java, hubo algunos participantes que utilizaron Ruby y RSpec. Invitamos a los que estuvieron trabajando con estas herramientas a que nos cuenten sus impresiones.

Finalmente, queremos volver a agradecer a todos los involucrados, por su buena onda, su energia y sus ganas. Sabemos que es un esfuerzo significativo dedicarse a aprender uno mismo, con el objetivo de mejorar profesionalmente, pero vale la pena. Tambien decirles que ya nos estamos poniendo a pensar en futuros encuentros. Hasta la proxima!

lunes, 2 de agosto de 2010

Code Smells

De chiquitos aprendemos que hay que evitar lo que tiene mal olor. La madre que huele el pañal de su hijo sabe que si apesta tiene que cambiarlo, y si apesta mucho, mucho, quizás el bebe tenga algún problemita de diarrea o algo similar. Sentido común universal este que sin embargo no aplicamos frecuentemente en el desarrollo de software.

A lo largo de la corta pero no menos exitosa vida de este Blog hemos mencionado varias veces a los Code Smells, sin aclarar su significado, mea culpa, porque a veces damos por sentado conceptos que son muy útiles. Este post entonces intenta corregir semejante falta:

Code Smells: (Aplicado al arte de la programación) son todos los síntomas que podemos encontrar en el código fuente de un sistema que indican que muy probablemente existan problemas más profundos de calidad de código, de diseño o de ambos.

El termino fue acuñado por Kent Beck y Ward Cunninghan hacia fines de los 90 y su uso se popularizó a partir del libro de Martin Fowler y otros: "Refactoring, Improving the Design of the existing Code" .

Si pensamos las barbaridades que hemos visto en algunos códigos fuentes (siempre de compañeros, nunca nuestros... :O ) y asociamos esa sensación de horror y rechazo con el olor a podrido de algunos ríos contaminados (Nota para los argentinos: Iba a poner Riachuelo pero no se si el resto del mundo conoce la fama "olorosa" de nuestro querido río, querido porque esta en la Boca ;), decía, esa imagen gráfica claramente lo que significa Code Smell, síntoma de que algo Apesta, y como al pañal, hay que cambiarlo.

En la práctica, históricamente, los Code Smells se han usado para identificar partes del código de baja calidad a las que aplicar las técnicas de refactoring explicadas en el libro arriba mencionado. Es decir, el libro describe un conjunto largo de distintas técnicas de transformación de código sin alterar su significado, las asi llamadas "Técnicas de Refactoting" y los Code Smells son los que indican en que parte del código hay que revisar como candidatas para aplicar esas técnicas.

No es la intención de este post describir uno a uno los Code Smells porque eso llevaría demasiada "tinta virtual" pero prometemos ir describiendo los Smells principales ya que consideramos (y bueno, no seria nada nosotros pero también lo piensa la gente más brillante de nuestro campo de la programación) que es uno de los conceptos fundamentales de la programación de estos tiempos, de los más aplicables y útiles, en combinación con los tests de unidad y que mayor impacto tienen tanto para la productividad como para la calidad de los sistemas que generemos.

Los principales Code Smells que se pueden encontrar en el código son :

  1. Código Duplicado
  2. Parámetros Booleanos
  3. Métodos Largos
  4. Clases Largas
  5. Larga Lista de Parámetros
  6. Cambio Divergente
  7. Shotgun Surgery (traducido algo asi como "cirugía a escopetazos")
  8. Feature Envy (textualmente: "envidia de features")
  9. Data Clumps (algo asi como aglomeracion de datos, masa de datos, en sintesis: un bodoque de datos (Arg))
  10. Obsesión por primitivos
  11. Generalización especulativa
  12. Campos temporales
  13. Refused Bequest ( algo asi como Rechazo a recibir parte de una herencia)
  14. Etc. (porque hay muchos mas).

Sigan sintonizando nuestro canal http://trainedchimpanzees.blogspot.com , porque vamos a estar analizando los mismos en sucesivos posts. De hecho ya empezamos a describir los Code Smells, uno de los primeros posts que hicimos fue sobre el Smell "Parametros Booleanos".

Para terminar una frase atribuida a la abuela de Beck, tómenla como consejo:

"Si apesta, hay que cambiarlo!"

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.