¿Cómo probar que no se lanza ninguna excepción?

Sé que una forma de hacerlo sería:

@Test public void foo(){ try{ //execute code that you expect not to throw Exceptions. } catch(Exception e){ fail("Should not have thrown any exception"); } } 

¿Hay alguna forma más limpia de hacer esto? (¿Probablemente usando la @Rule de Junit?)

Te estás acercando a esto de la manera incorrecta. Simplemente pruebe su funcionalidad: si se lanza una excepción, la prueba fallará automáticamente. Si no se lanza ninguna excepción, sus pruebas aparecerán verdes.

Me he dado cuenta de que esta pregunta despierta interés de vez en cuando, así que ampliaré un poco.

Antecedentes de las pruebas unitarias

Cuando realiza pruebas unitarias, es importante definirse a sí mismo lo que considera una unidad de trabajo. Básicamente: una extracción de su base de código que puede incluir o no múltiples métodos o clases que representan una sola pieza de funcionalidad.

O, como se define en El arte de las pruebas unitarias, segunda edición de Roy Osherove , página 11:

Una prueba de unidad es una pieza de código automatizada que invoca la unidad de trabajo que se prueba y luego verifica algunas suposiciones sobre un resultado final único de esa unidad. Una prueba de unidad casi siempre se escribe usando un marco de prueba de unidad. Se puede escribir fácilmente y se ejecuta rápidamente. Es confiable, legible y mantenible. Es consistente en sus resultados siempre que el código de producción no haya cambiado.

Lo importante es darse cuenta de que una unidad de trabajo por lo general no es solo un método, sino que en el nivel básico es un método y luego está encapsulado por otra unidad de trabajo.

enter image description here

Idealmente, debe tener un método de prueba para cada unidad de trabajo por separado, de modo que siempre pueda ver inmediatamente dónde van las cosas mal. En este ejemplo, hay un método básico llamado getUserById() que devolverá un usuario y hay un total de 3 unidades de trabajos.

La primera unidad de trabajo debe probar si se devuelve o no un usuario válido en el caso de una entrada válida y no válida.
Todas las excepciones que arroje el origen de datos se deben tratar aquí: si no hay un usuario presente, debe haber una prueba que demuestre que se produce una excepción cuando no se puede encontrar al usuario. Una muestra de esto podría ser la IllegalArgumentException capturada con la @Test(expected = IllegalArgumentException.class) .

Una vez que haya manejado todas sus cajas de uso para esta unidad básica de trabajo, sube de nivel. Aquí hace exactamente lo mismo, pero solo maneja las excepciones que provienen del nivel justo debajo del actual. Esto mantiene su código de prueba bien estructurado y le permite recorrer rápidamente la architecture para encontrar dónde van las cosas mal, en lugar de tener que saltar por todos lados.

Manejando una entrada válida y defectuosa de las pruebas

En este punto, debe quedar claro cómo vamos a manejar estas excepciones. Hay 2 tipos de entrada: entrada válida y entrada defectuosa (la entrada es válida en sentido estricto, pero no es correcta).

Cuando trabajas con entradas válidas , estás configurando la expectativa implícita de que cualquier prueba que escribas funcionará.

Tal llamada a un método puede verse así: existingUserById_ShouldReturn_UserObject . Si este método falla (por ejemplo, se lanza una excepción), entonces sabrá que algo salió mal y podrá comenzar a cavar.

Al agregar otra prueba ( nonExistingUserById_ShouldThrow_IllegalArgumentException ) que utiliza la entrada defectuosa y espera una excepción, puede ver si su método hace lo que se supone que debe hacer con una entrada incorrecta.

TL; DR

Intentabas hacer dos cosas en tu prueba: verificar si la entrada es válida y defectuosa. Al dividir esto en dos métodos que cada uno hace una cosa, tendrá pruebas mucho más claras y una mejor visión general de dónde van las cosas mal.

Al mantener en mente la unidad de trabajos en capas, también puede reducir la cantidad de pruebas que necesita para una capa que es más alta en la jerarquía porque no tiene que dar cuenta de todo lo que podría haber fallado en las capas inferiores: el las capas debajo de la actual son una garantía virtual de que sus dependencias funcionan y si algo sale mal, está en su capa actual (suponiendo que las capas inferiores no arrojen ningún error).

Me encontré con esto debido a la regla de SonarQubes “squid: S2699”: “Agregar al menos una afirmación a este caso de prueba”.

Tuve una prueba simple de que el único objective que era, que pasó sin excepción.

Imagina este código simple:

 public class Printer { public static void printLine(final String line) { System.out.println(line); } } 

¿Qué tipo de afirmación se puede agregar para probar este método? Claro, puedes intentar atraparlo, pero eso es solo un código inflado.

La solución te da JUnit en sí mismo.

En los casos en que no se lanza una excepción y desea ilustrar explícitamente este comportamiento, simplemente agregue el esperado como el siguiente:

 @Test(expected = Test.None.class /* no exception expected */) public void test_printLine() { Printer.printLine("line"); } 

Test.None.class es el valor predeterminado para el valor esperado.

Java 8 hace que esto sea mucho más fácil, y Kotlin / Scala lo hace doblemente.

Podemos escribir una pequeña clase de utilidad

 class MyAssertions{ public static void assertDoesNotThrow(FailingRunnable action){ try{ action.run() } catch(Exception ex){ throw new Error("expected action not to throw, but it did!", ex) } } } @FunctionalInterface interface FailingRunnable { void run() throws Exception } 

y luego tu código se convierte simplemente:

 @Test public void foo(){ MyAssertions.assertDoesNotThrow(() -> { //execute code that you expect not to throw Exceptions. } } 

Si no tienes acceso a Java-8, utilizaría una instalación java muy antigua: bloques de código aribitrary y un simple comentario

 //setup Component component = new Component(); //act configure(component); //assert /*assert does not throw*/{ component.doSomething(); } 

Y finalmente, con kotlin, un lenguaje del que me he enamorado recientemente:

 fun (() -> Any?).shouldNotThrow() = try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) } @Test fun `when foo happens should not throw`(){ //... { /*code that shouldn't throw*/ }.shouldNotThrow() } 

Aunque hay mucho espacio para jugar con exactamente cómo desea express esto, siempre fui un fanático de las afirmaciones fluidas .


Respecto a

Te estás acercando a esto de la manera incorrecta. Simplemente pruebe su funcionalidad: si se lanza una excepción, la prueba fallará automáticamente. Si no se lanza ninguna excepción, sus pruebas aparecerán verdes.

Esto es correcto en principio pero incorrecto en conclusión.

Java permite excepciones para el flujo de control. Esto se realiza mediante el tiempo de ejecución de JRE en API como Double.parseDouble través de NumberFormatException y Paths.get través de InvalidPathException .

Dado que ha escrito un componente que valida cadenas de Double.ParseDouble para Double.ParseDouble , tal vez usando un Regex, tal vez un analizador escrito a mano, o tal vez algo que incorpore algunas otras reglas de dominio que restrinja el rango de un doble a algo específico, cuál es la mejor para probar este componente? Creo que una prueba obvia sería afirmar que, cuando se analiza la cadena resultante, no se lanza ninguna excepción. Escribiría esa prueba usando el assertDoesNotThrow o /*comment*/{code} . Algo como

 @Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){ //setup String input = "12.34E+26" //a string double with domain significance //act boolean isValid = component.validate(input) //assert -- using the library 'assertJ', my personal favourite assertThat(isValid).describedAs(input + " was considered valid by component").isTrue(); assertDoesNotThrow(() -> Double.parseDouble(input)); } 

También le recomendaría que parametrice esta prueba en la input usando Theories o Parameterized para que pueda reutilizar esta prueba más fácilmente para otras entradas. Alternativamente, si quieres ser exótico, puedes optar por una herramienta de generación de pruebas (y esto ). TestNG tiene mejor soporte para pruebas parametrizadas.

Lo que encuentro particularmente desagradable es la recomendación de usar @Test(expectedException=IllegalArgumentException.class) , esta excepción es peligrosamente amplia . Si su código cambia de tal manera que el componente bajo el constructor de la prueba tiene if(constructorArgument < = 0) throw IllegalArgumentException() , y su prueba estaba proporcionando 0 para ese argumento porque era conveniente --y esto es muy común, porque buena generación de datos de prueba es un problema sorprendentemente difícil--, entonces su prueba será una barra verde a pesar de que no prueba nada. Tal prueba es peor que inútil.

Si tiene la mala suerte de detectar todos los errores en su código. Puedes hacer estúpidamente

 class DumpTest { Exception ex; @Test public void testWhatEver() { try { thisShouldThroughError(); } catch (Exception e) { ex = e; } assertEquals(null,ex); } } 

Con AssertJ fluent assertions 3.7.0 :

 Assertions.assertThatCode(() -> toTest.method()) .doesNotThrowAnyException(); 

JUnit5 agrega el método assertAll () para este propósito exacto.

 assertAll( () -> foo() ) 

fuente: JUnit 5 API

Puede hacerlo utilizando una @Rule y luego llamar al método reportMissingExceptionWithMessage como se muestra a continuación: Esto es Scala, pero se puede hacer fácilmente de manera similar en Java.

enter image description here

Si desea probar si su objective de prueba consume la excepción. Simplemente abandone la prueba como (simulacro de colaboración usando jMock2):

 @Test public void consumesAndLogsExceptions() throws Exception { context.checking(new Expectations() { { oneOf(collaborator).doSth(); will(throwException(new NullPointerException())); } }); target.doSth(); } 

La prueba pasaría si su objective consume la excepción lanzada, de lo contrario la prueba fallaría.

Si desea probar su lógica de consumo de excepción, las cosas se vuelven más complejas. Sugiero delegar el consumo a un colaborador que podría ser burlado. Por lo tanto, la prueba podría ser:

 @Test public void consumesAndLogsExceptions() throws Exception { Exception e = new NullPointerException(); context.checking(new Expectations() { { allowing(collaborator).doSth(); will(throwException(e)); oneOf(consumer).consume(e); } }); target.doSth(); } 

Pero a veces está demasiado diseñado si solo quieres iniciar sesión. En este caso, este artículo ( http://java.dzone.com/articles/monitoring-declarative-transac , http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-here -an-easy-way / ) puede ayudar si insiste tdd en este caso.

Use assertNull (…)

 @Test public void foo() { try { //execute code that you expect not to throw Exceptions. } catch (Exception e){ assertNull(e); } } 

JUnit 5 (Júpiter) proporciona tres funciones para verificar la ausencia / presencia de excepción:

assertAll​()

Afirma que todos los executables suministrados
no arrojes excepciones.

assertDoesNotThrow​()

Afirma que la ejecución de la
executable / supplier
no arroja ningún tipo de excepción .

Esta función está disponible
desde JUnit 5.2.0 (29 de abril de 2018).

assertThrows​()

Afirma que la ejecución del executable suministrado
arroja una excepción del tipo expectedType
y devuelve la excepción .

Ejemplo

 package test.mycompany.myapp.mymodule; import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; class MyClassTest { @Test void when_string_has_been_constructed_then_myFunction_does_not_throw() { String myString = "this string has been constructed"; assertAll(() -> MyClass.myFunction(myString)); } @Test void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() { String myString = "this string has been constructed"; assertDoesNotThrow(() -> MyClass.myFunction(myString)); } @Test void when_string_is_null_then_myFunction_throws_IllegalArgumentException() { String myString = null; assertThrows( IllegalArgumentException.class, () -> MyClass.myFunction(myString)); } } 

Puede esperar que la excepción no se genere al crear una regla.

 @Rule public ExpectedException expectedException = ExpectedException.none(); 

Lo siguiente falla la prueba para todas las excepciones, marcadas o desmarcadas:

 @Test public void testMyCode() { try { runMyTestCode(); } catch (Throwable t) { throw new Error("fail!"); } }