Pregunta

Mi lema para Java es "sólo porque Java ha bloques estáticos, esto no significa que usted debe ser el uso de ellos." Bromas aparte, hay un montón de trucos en Java que hacer pruebas en una pesadilla.Dos de los más odio son las Clases Anónimas y Bloques Estáticos.Tenemos un montón de código heredado que hacer uso de Bloques Estáticos y estos son uno de los molestos puntos en nuestra inserción en la escritura de pruebas de unidad.Nuestro objetivo es ser capaz de escribir las pruebas unitarias para las clases que dependen de esta inicialización estática con cambios mínimos en el código.

Hasta ahora mi sugerencia a mis colegas es mover el cuerpo del bloque estático en un método estático y lo llaman staticInit.Este método puede entonces ser llamado desde dentro del bloque estático.Para la unidad de pruebas de otra clase que depende de esta clase podría fácilmente simulacro staticInit con JMockit para no hacer nada.Vamos a ver en este ejemplo.

public class ClassWithStaticInit {
  static {
    System.out.println("static initializer.");
  }
}

Será cambiado a

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

Así que podemos hacer lo siguiente en un JUnit.

public class DependentClassTest {
  public static class MockClassWithStaticInit {
    public static void staticInit() {
    }
  }

  @BeforeClass
  public static void setUpBeforeClass() {
    Mockit.redefineMethods(ClassWithStaticInit.class, MockClassWithStaticInit.class);
  }
}

Sin embargo, esta solución también viene con sus propios problemas.No se puede ejecutar DependentClassTest y ClassWithStaticInitTest en la misma JVM puesto que usted realmente desea el bloque estático a ejecutar para ClassWithStaticInitTest.

¿Cuál sería su forma de llevar a cabo esta tarea?O mejor, no JMockit soluciones basadas en que usted piensa que sería el trabajo más limpio?

¿Fue útil?

Solución

Cuando me encuentro con este problema, yo suelo hacer la misma cosa que usted describe, excepto que hacer el método estático protegido para que yo pueda invocar manualmente.En la parte superior de esto, estoy seguro de que el método puede ser invocado varias veces sin problemas (de lo contrario, no es mejor que el inicializador estático como mucho ya que las pruebas van).

Esto funciona razonablemente bien, y que en realidad puede probar que el inicializador estático método no es lo que esperan, lo que quiero hacer.A veces es más fácil tener algunos de inicialización estática de código, y es simplemente no vale la pena construir una excesivamente complejo sistema para reemplazarlo.

Cuando se utiliza este mecanismo, yo asegúrese de documentar que el método protegido está expuesto sólo para propósitos de prueba, con la esperanza de que no va a ser utilizado por otros desarrolladores.Por supuesto, esto puede no ser una solución viable, por ejemplo, si la clase de la interfaz es visible externamente (ya sea como un sub-componente de algún tipo para otros equipos, o como un marco público).Es una solución simple para el problema, sin embargo, y no requiere de una biblioteca de terceros para configurar (que me gusta).

Otros consejos

PowerMock es otro simulacro de marco que se extiende EasyMock y Mockito.Con PowerMock usted puede fácilmente eliminar un comportamiento no deseado a partir de una clase, por ejemplo un inicializador estático.En su ejemplo, usted sólo tiene que añadir las siguientes anotaciones a su JUnit caso de prueba:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("some.package.ClassWithStaticInit")

PowerMock no utiliza Java agente y por lo tanto no requiere la modificación de la JVM parámetros de inicio.Simplemente agregue el archivo jar y por encima de anotaciones.

Esto va a entrar en más "Avanzados" JMockit.Resulta, se puede redefinir la inicialización estática bloques en JMockit mediante la creación de un public void $clinit() método.Así, en lugar de hacer este cambio

public class ClassWithStaticInit {
  static {
    staticInit();
  }

  private static void staticInit() {
    System.out.println("static initialized.");
  }
}

bien podríamos dejar ClassWithStaticInit como es y hacer lo siguiente en el MockClassWithStaticInit:

public static class MockClassWithStaticInit {
  public void $clinit() {
  }
}

Este será, de hecho, nos permiten no hacer cambios en las clases existentes.

En ocasiones, me parece estático initilizers en las clases que mi código depende.Si no puedo refactorizar el código, yo uso PowerMock's @SuppressStaticInitializationFor anotación para suprimir el inicializador estático:

@RunWith(PowerMockRunner.class)
@SuppressStaticInitializationFor("com.example.ClassWithStaticInit")
public class ClassWithStaticInitTest {

    ClassWithStaticInit tested;

    @Before
    public void setUp() {
        tested = new ClassWithStaticInit();
    }

    @Test
    public void testSuppressStaticInitializer() {
        asserNotNull(tested);
    }

    // more tests...
}

Lea más acerca de la supresión de comportamientos no deseados.

Descargo de responsabilidad:PowerMock es un proyecto de código abierto desarrollado por dos de sus compañeros de la mina.

A mí me parece que son el tratamiento de un síntoma:un mal diseño con las dependencias de la inicialización estática.Tal vez algunos de refactorización es la verdadera solución.Parece que ya he hecho un poco de refactorización con su staticInit() la función, pero tal vez la función que se necesita para ser llamado desde el constructor, no de un inicializador estático.Si usted puede hacer lejos con inicializadores estáticos período, usted estará mejor.Sólo usted puede tomar esta decisión (Yo no puede ver su código fuente), pero algunos de refactorización sin duda le ayudará.

Como de burla, yo uso EasyMock, pero se han topado con el mismo problema.Efectos secundarios de los inicializadores estáticos en el código de la herencia hacer pruebas difíciles.Nuestra respuesta fue que refactorizar el inicializador estático.

Puedes escribir tu código de prueba en Groovy y fácilmente se burlan de la estática utilizando el método de metaprogramación.

Math.metaClass.'static'.max = { int a, int b -> 
    a + b
}

Math.max 1, 2

Si usted no puede utilizar Groovy, usted realmente va a necesitar para la refactorización de código (tal vez para inyectar algo así como un initializator).

Saludos

Supongo que usted realmente desea algún tipo de fábrica en lugar de la estática de inicializador.

Algunas mezcla de un singleton y un resumen de la fábrica probablemente sería capaz de obtener la misma funcionalidad que hoy en día, y con buena capacidad de prueba, pero que habría que añadir un buen montón de caldera-placa de código, así que sería mejor tratar de refactorizar la estática lejos de cosas completamente o si al menos podría hacer algo menos compleja solución.

Difícil decir si es posible sin ver el código, aunque.

No estoy super bien informado en el Simulacro de marcos, así que por favor me corrija si estoy equivocado, pero no podía usted posiblemente tiene dos diferentes objetos ficticios para cubrir las situaciones en las que usted menciona?Como

public static class MockClassWithEmptyStaticInit {
  public static void staticInit() {
  }
}

y

public static class MockClassWithStaticInit {
  public static void staticInit() {
    System.out.println("static initialized.");
  }
}

Entonces usted puede hacer uso de ellos en sus diferentes casos de prueba

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithEmptyStaticInit.class);
}

y

@BeforeClass
public static void setUpBeforeClass() {
  Mockit.redefineMethods(ClassWithStaticInit.class, 
                         MockClassWithStaticInit.class);
}

respectivamente.

No es realmente una respuesta, pero sólo me preguntaba - ¿no hay alguna manera de "revertir" la llamada a la Mockit.redefineMethods?
Si no hay tal método explícito existe, no debe ejecutar de nuevo de la siguiente manera hacer el truco?

Mockit.redefineMethods(ClassWithStaticInit.class, ClassWithStaticInit.class);

Si ese método existe, puede ejecutarlo en la clase' @AfterClass el método, y la prueba de ClassWithStaticInitTest con el "original" inicializador estático bloque, como si nada ha cambiado, de la misma JVM.

Este es solo un presentimiento, aunque, así que puede ser que falta algo.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top