2009-09-23 10 views
6

Estoy trabajando con un código existente, tratando de agregarlo y aumentar las pruebas unitarias para él. Pero encontrarse con algunos problemas para hacer que el código sea comprobable.Diseñando constructores para Testability

Constructor original:

public Info() throws Exception 
{ 
    _ServiceProperties = new ServiceProperties(); 
    _SshProperties = new SshProperties(); 
} 

Soy consciente de que esto es malo, y obviamente no comprobable. En un entorno junit, esta clase no podrá crear todas las veces, ya que no podrá encontrar las propiedades necesarias para construirse. Ahora, soy consciente de que esta clase sería mucho más comprobable con el simple cambio de mover todo lo que está precedido por 'nuevo' como parámetro.

Así que terminan con:

Nuevo Constructor:

public Info(ServiceProperties srvProps, SshProperties sshProps) throws Exception 
{ 
    _ServiceProperties = srvProps; 
    _SshProperties = sshProps; 
} 

que me permite adecuadamente unidad de prueba esta clase Información. El problema, sin embargo, es ahora todo ese trabajo es empujado a alguna otra clase:

Método alguna otra clase:

public void useInfo() throws Exception 
{ 
    ServiceProperties srvProps = new ServiceProperties(); 
    SshProperties sshProps = new SshProperties(); 
    Info info = new Info(srvProprs, sshProprs); 
    doStuffWithInfo(info); 
} 

Ahora bien, este método no es comprobable. Todo lo que he logrado hacer es empujar hacia donde están ocurriendo las construcciones de estos objetos de Propiedad, y en algún otro lugar algún trozo de código va a estar atorado y de hecho tiene que llamar "nuevo".

Aquí está el problema para mí: no puedo encontrar la manera de romper esta cadena de eventos simplemente empujando estas "nuevas" llamadas a otro lugar. ¿Qué me estoy perdiendo?

Respuesta

7

Mire utilizando un marco de Inyección de Dependencia como Spring. Esta aplicación de Inversión de control significa que cada uno de sus tipos puede evitar la trampa que ha visto, dejando la configuración para "conectar" componentes juntos.

Este introduction to Spring (pdf) ofrece una descripción completa de Spring. El primer capítulo o dos deberían ser suficientes para explicar los conceptos.

Véase también Inversion of Control Containers and the Dependency Injection pattern por Martin Fowler

+0

También puede echar un vistazo a nuestra Material del curso de primavera: http://www.trainologic.org/courses/info/2 –

0

La respuesta más fácil es la primavera. Sin embargo, otra respuesta es poner sus cosas de configuración en JNDI. La primavera en algunos aspectos es una mejor respuesta, especialmente si no tiene nada que cambie dependiendo del entorno.

2

Tiene la idea correcta. Tal vez esto te ayude. Le recomiendo que siga dos reglas para todas sus clases de importancia, donde "significativo" significa que si no sigue los pasos, será más difícil probar, reutilizar o mantener la clase. Estas son las reglas:

  1. Nunca instancia o auto-adquirir una dependencia
  2. siempre a las interfaces de programa de

Usted tiene un comienzo en la regla # 1. Cambió su clase Info para dejar de crear sus dependencias. Por "dependencia" me refiero a otras clases, datos de configuración cargados desde archivos de propiedades o lo que sea, etc.Cuando dependes de cómo se crea una instancia de algo, estás atando a tu clase y haciendo que sea más difícil de probar, reutilizar y mantener. Entonces, incluso si una dependencia se crea a través de una fábrica o un singleton, no haga que su clase lo cree. Si tiene otra cosa, llame al create() o getInstance() o lo que sea y páselo.

Así que eligió el "algo más" para ser la clase que usa su clase y se dio cuenta de que tiene un mal olor. El remedio es tener en su lugar el punto de entrada a su instancia de instancia de todas las dependencias. En una aplicación java tradicional, este es su método main(). si lo piensas, crear instancias de clases y conectarlas entre sí, o "cablearlas" juntas, es un tipo especial de lógica: lógica de "ensamblaje de aplicaciones". ¿Es mejor difundir esta lógica a través de su código, o recogerlo en un lugar para mantenerlo más fácilmente? La respuesta es que recopilarlo en un solo lugar es mejor, no solo para el mantenimiento, sino que el hacerlo convierte a todas las clases de importancia en componentes más útiles y flexibles.

En su main() o su equivalente de main() debe crear todos los objetos que necesita, pasándolos a los setters y constructores de los demás para "conectarlos" entre sí. Las pruebas de su unidad las cablearían de manera diferente, pasando objetos simulados o cosas similares. El acto de hacer todo esto se llama "inyección de dependencia". Después de hacer lo que digo, es probable que tengas un gran método feo main(). Aquí es donde una herramienta de inyección de dependencia puede ayudarlo y, de hecho, hacer que su código sea infinitamente más flexible. La herramienta que sugiero cuando llegas a este punto, como otros también han sugerido, es Spring.

La regla menos importante # 2 - Siempre programa para las interfaces, sigue siendo muy importante, ya que elimina todas las dependencias de aplicación, por lo que la reutilización mucho más fácil, por no mencionar el aprovechamiento de otras herramientas como marcos simulacros de objetos, marcos ORM más fáciles, etc. también.

1

Incluso los marcos de inyección de dependencias como Spring, Guice, PicoContainer, etc. necesitan algún tipo de boostrap, por lo que siempre hay que construir algo.

Le sugiero que use un proveedor/fábrica que devuelva una instancia configurada de su clase. Esto le permitiría salir de la jerarquía de "creación".

0

Haces que la otra clase tenga demasiado conocimiento sobre la clase de información y sus dependencias. Un marco de inyección de dependencia usaría una clase de proveedor. El uso de tipos genéricos se puede hacer un proveedor de objetos Info:

interface Provider<T> { 
    T get(); 
} 

Si su alguna-otra clase toma un proveedor < Información > en su constructor de su método se vería así:

public void useInfo() throws Exception 
{ 
    Info info = infoProvider.get(); 
    doStuffWithInfo(info); 
} 

Esto tiene eliminó la construcción de clases concretas de su código. El siguiente paso es convertir Info en una interfaz para facilitar la creación de un simulacro para el caso específico de prueba de unidad.

Y sí, esto empujará y empujará todo el código de construcción del objeto más y más. Llevará a algún módulo que solo describa cómo unir cosas. Las "reglas" para dicho código es que debería estar libre de enunciados y bucles condicionales.

Recomiendo leer Misko Heverys blog. Todas las presentaciones son útiles e imprimen la guía para escribir código comprobable como un pequeño libro de reglas una vez que comprenda las reglas es algo bueno.

1

Sus constructores no son incorrectos y el problema no es sobre cuándo/dónde se ejecuta el código, sino sobre lo que todos los demás mencionaron: Dependency Injection. Necesita crear objetos simulados de SshProperties para crear una instancia de su objeto. La forma más sencilla (suponiendo que la clase no está marcado como final) es extender la clase:

public class MockSshProperties extends SshProperties { 
    // implemented methods 
} 

Puede usted utilizar marcos simulados como Mockito:

public class Info { 

    private final sshProps; 
    private final serviceProps; 

    public Info() { 
     this(new SshProperties(), new ServiceProperties()); 
    } 

    public Info(SshProperties arg1, ServiceProperties arg2) { 
     this.sshProps = arg1; 
     this.serviceProps = arg2 
    } 
} 

public class InfoTester 
{ 
    private final static SshProperties sshProps = mock(SshProperties.class); 
    private final static ServiceProperties serviceProps = mock(ServiceProperties.class); 
    static { 
     when(sshProps.someGetMethod("something")).thenReturn("value"); 
    } 

    public static void main(String[] args) { 
     Info info = new Info(sshProps, serviceProps); 
     //do stuff 
    } 
}