java junit junit4 junit-rule

java - Combinando @ClassRule y @Rule en JUnit 4.11



junit4 junit-rule (5)

He experimentado problemas similares hay dos formas de trabajo. No me gusta ninguno, pero tienen diferentes compromisos:

1) Si su Regla expone los métodos de limpieza, puede invocar manualmente la limpieza dentro de un método @Before .

@ClassRule public static MyService service = ... ; @Before public void cleanupBetweenTests(){ service.cleanUp(); }

El inconveniente de esto es que debe recordar (y decirle a otros miembros de su equipo) que siempre agregue el método @Before o cree una clase abstracta de la que se hereden sus pruebas para que haga la limpieza por usted.

2) Tener 2 campos, uno estático, uno no estático que apunta al mismo objeto, cada campo anotado por @ClassRule o @Rule respectivamente. Esto es necesario si la limpieza no está expuesta. La desventaja, por supuesto, es que también debes recordar tener tanto un @ClassRule como un @Rule que apunten a lo mismo que parece extraño.

@ClassRule public static MyService service = ... ; @Rule public MyService tmp = service ;

Luego, en su implementación, debe diferenciar entre un conjunto de pruebas o una única prueba. Esto se puede hacer verificando si la Description tiene hijos. Dependiendo de cuál, crea diferentes adaptadores de Statement para manejar la limpieza o no:

@Override protected void after() { //class-level shut-down service shutdownService(); } @Override protected void before() { //class-level init service initService(); } @Override public Statement apply(Statement base, Description description) { if(description.getChildren().isEmpty()){ //test level perform cleanup return new CleanUpStatement(this,base); } //suite level no-change return super.apply(base, description); }

Aquí está la clase de Statement personalizada para limpiar antes de cada prueba:

private static final class CleanUpStatement extends Statement{ private final MyService service; private final Statement statement; CleanUpStatement(MyService service, Statement statement) { this.service = service; this.statement = statement; } @Override public void evaluate() throws Throwable { //clear messages first myService.cleanUp(); //now evaluate wrapped statement statement.evaluate(); } }

Después de todo eso, me inclino más hacia la opción 1, ya que es más reveladora y tiene menos código que mantener. También me preocuparía que otros intenten modificar el código en la opción 2 pensando que hubo un error ya que el mismo campo está apuntado dos veces. El esfuerzo extra, los comentarios en código, etc. no valen la pena.

De todos modos, todavía tiene placa de caldera para copiar y pegar en todas partes o clases abstractas usando un método de plantilla.

En JUnit 4.10 y más abajo, es posible anotar una regla como @Rule y @ClassRule. Esto significa que la regla se invoca antes antes / después de la clase, y antes / después de cada prueba. Una posible razón para hacerlo es configurar un recurso externo costoso (a través de las llamadas @ClassRule) y luego restablecerlo a bajo costo (a través de las llamadas a @Rule).

A partir de JUnit 4.11, los campos @Rule deben ser no estáticos y los campos @ClassRule deben ser estáticos, por lo que ya no es posible.

Existen claramente soluciones alternativas (por ejemplo, separar explícitamente las responsabilidades de @ClassRule y @Rule en reglas separadas), pero parece una pena tener que imponer el uso de dos reglas. Analicé brevemente el uso de @Rule e deduzco si es o no la primera / última prueba, pero no creo que la información esté disponible (al menos, no está disponible directamente en la Descripción).

¿Existe una forma limpia y ordenada de combinar las funciones @ClassRule y @Rule en una sola regla en JUnit 4.11?

Gracias, Rowan


La respuesta para su pregunta es la siguiente: no hay una forma clara de hacerlo (solo configure dos reglas simultáneamente). Intentamos implementar tareas similares para los reintentos de pruebas automáticas, y se combinaron dos reglas (para esta tarea) y se implementó un enfoque tan feo: las pruebas reintentan con dos reglas

Pero si piensa más precisamente en la tarea necesaria (que debe implementarse), se puede utilizar un mejor enfoque utilizando jUnit custom Runner: Reintentar Runner .

Por lo tanto, para tener un mejor enfoque será bueno conocer su caso de uso específico.


Otra posible solución es declarar una @ClassRule estática y usar ese valor al declarar una @Rule no estática, también:

@ClassRule public static BeforeBetweenAndAfterTestsRule staticRule = new BeforeBetweenAndAfterTestsRule(); @Rule public BeforeBetweenAndAfterTestsRule rule = staticRule;

Eso significa que no tiene que refactorizar ninguna clase de regla existente, pero aún necesita declarar dos reglas, por lo que no responde a la pregunta original particularmente bien.


Otra posible solución es declarar una @Rule no estática y hacer que actúe sobre colaboradores estáticos: si el colaborador aún no está inicializado, @Rule sabe que se está ejecutando por primera vez (para que pueda configurar sus colaboradores, por ejemplo, iniciar un recurso externo); si se inicializan, @Rule puede hacer su trabajo por prueba (por ejemplo, restablecer un recurso externo).

Esto tiene el inconveniente de que @Rule no sabe cuándo se procesó la última prueba, por lo que no puede realizar ninguna acción posterior a la clase (por ejemplo, ordenar el recurso externo); sin embargo, se puede usar un método @AfterClass para hacerlo.


A partir de JUnit 4.12 (no publicado al momento de la escritura), será posible anotar una sola regla estática con @Rule y @ClassRule .

Tenga en cuenta que debe ser estático: una regla no estática anotada con @Rule y @ClassRule todavía se considera inválida (ya que cualquier cosa anotada @ClassRule funciona a nivel de clase, por lo que solo tiene sentido como miembro estático).

Vea las notas de la versión y mi solicitud de extracción si está interesado en más detalles.