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

238

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 Junit's @Rule?)

Ankit Dhingra
fuente
10
Se considera que una prueba JUnit ha fallado si arroja una excepción que no sea una excepción esperada. Por lo general, no se esperan excepciones.
Raedwald
¿No hay una distinción entre falla y error en JUnit? El primero significa que la prueba falló, el segundo significa que sucedió algo inesperado.
Vituel
1
posible duplicado de ¿Cómo puedo probar si no se produce una excepción en particular?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

198

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

He notado que esta pregunta genera interés de vez en cuando, así que me expandiré un poco.

Antecedentes de las pruebas unitarias

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

O, como se define en The art of Unit Testing, 2nd Edition 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 está probando y luego verifica algunos supuestos sobre un resultado final único de esa unidad. Una prueba unitaria casi siempre se escribe utilizando un marco de prueba unitaria. 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 generalmente no es solo un método, sino que en el nivel más básico es un método y luego se encapsula en otra unidad de trabajo.

ingrese la descripción de la imagen aquí

Idealmente, debe tener un método de prueba para cada unidad de trabajo por separado para que siempre pueda ver de inmediato 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 trabajo.

La primera unidad de trabajo debe probar si se devuelve o no un usuario válido en el caso de una entrada válida o no válida.
Aquí se deben manejar las excepciones lanzadas por el origen de datos: si no hay ningún 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 IllegalArgumentExceptionque se captura con la @Test(expected = IllegalArgumentException.class)anotación.

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

Manejo de una entrada válida y defectuosa de las pruebas

En este punto, debería quedar claro cómo manejaremos 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 trabaja con una entrada válida , está estableciendo la expectativa implícita de que cualquier prueba que escriba funcionará.

Tal llamada al método puede tener este aspecto: existingUserById_ShouldReturn_UserObject. Si este método falla (por ejemplo, se produce una excepción), entonces sabe que algo salió mal y puede comenzar a cavar.

Al agregar otra prueba ( nonExistingUserById_ShouldThrow_IllegalArgumentException) que usa 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

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

Al tener en cuenta la unidad de trabajo 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 tener en cuenta todo lo que podría haber salido mal en las capas inferiores: 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).

Jeroen Vannevel
fuente
2
La cosa es que estoy tratando de TDD y uno de los colaboradores que uso está lanzando una excepción. Así que necesito probar el hecho de que estoy consumiendo la excepción lanzada por el colaborador
Ankit Dhingra
66
¿Estás diciendo que tu funcionalidad depende del manejo de una excepción? Ese es un olor a código: hay excepciones para permitirle atrapar problemas con elegancia; No se utilizan para el control de flujo. Si desea probar un escenario en el que se debe lanzar una excepción, entonces debe usar la expectedanotación. Si desea probar un escenario en el que falla su código y desea ver si el error se maneja correctamente: use expectedy tal vez use aserciones para determinar si se ha resuelto.
Jeroen Vannevel
El problema es que no puedo recuperarme de la excepción que ha ocurrido en el colaborador y todo lo que hago es registrar el problema usando un log.debug ("Mensaje de error"). Por lo tanto, no hay efectos secundarios que sucedan como parte del bloque catch que posiblemente pueda afirmar.
Ankit Dhingra
55
@JeroenVannevel es perfectamente válido probar que una situación de error que causa una excepción se maneja correctamente.
Thorbjørn Ravn Andersen
1
@dpk si puedes. Añades throws IllegalArgumentExceptiona tu prueba. Lo que quiere al final, es que su prueba se ponga roja si hay una excepción. ¿Bien adivina que? No necesitas escribir fail(). Como escribió @Jeroen Vannevel: "si se produce una excepción, la prueba fallará automáticamente".
Amedee Van Gasse
132

Me topé con esto debido a la regla de SonarQube "calamar: S2699": "Agregue al menos una afirmación a este caso de prueba".

Tuve una prueba simple cuyo único objetivo era pasar sin lanzar excepciones.

Considere 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 hacer un intento de atraparlo, pero eso es solo una acumulación de código.

La solución proviene de JUnit en sí.

En caso de que no se produzca ninguna excepción y desee ilustrar explícitamente este comportamiento, simplemente agregue expectedcomo en el siguiente ejemplo:

@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.

Sven Döring
fuente
30
Creo que esta es la mejor respuesta. La respuesta aceptada es excelente, y el autor es correcto al señalar el olor del código. Sin embargo, realmente no respondió la pregunta específica.
HellishHeat
44
Es interesante observar que el valor predeterminado para el esperado es Ninguno, por lo que simplemente anotar el método con @Test sería suficiente.
oziomajnr
42

Con afirmaciones fluidas AssertJ 3.7.0 :

Assertions.assertThatCode(() -> toTest.method())
    .doesNotThrowAnyException();
denu
fuente
1
@emeraldhieu La pregunta ni siquiera es acerca de afirmar J, entonces, ¿cómo responde esto "directamente" a la pregunta de OP?
MauriceNino
41

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

assertAll​()

Afirma que todos los suministrados executables
  no arrojan excepciones.

assertDoesNotThrow​()

Afirma que la ejecución del
  suministrado 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 de los executable
lanzamientos suministrados genera una excepción 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));
    }

}
olibre
fuente
1
Esta es la mejor respuesta ahora. Otras respuestas están discutiendo versiones anteriores de JUnit
Tejesh Raut
29

Java 8 hace esto mucho más fácil, y Kotlin / Scala 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 vuelve simplemente:

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

Si no tiene acceso a Java-8, usaría una instalación java dolorosamente antigua: bloques de código arbitrario y un simple comentario

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}

Y finalmente, con kotlin, un idioma 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 quieres expresar 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 produce una excepción, la prueba fallará automáticamente. Si no se produce ninguna excepción, todas sus pruebas se volverán verdes.

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

Java permite excepciones para el flujo de control. Esto se hace por el mismo tiempo de ejecución JRE en las API como Double.parseDoublea través de un NumberFormatExceptione Paths.gettravés de una InvalidPathException.

Dado que ha escrito un componente que valida las cadenas de números para Double.ParseDouble, tal vez utilizando un Regex, tal vez un analizador escrito a mano, o tal vez algo que incorpore otras reglas de dominio que restrinjan el rango de un doble a algo específico, la mejor manera de probar esto ¿componente? Creo que una prueba obvia sería afirmar que, cuando se analiza la cadena resultante, no se produce ninguna excepción. Escribiría esa prueba usando lo anterior assertDoesNotThrowo el /*comment*/{code}bloque. 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 animo a que parametrice esta prueba al inputusar Theorieso Parameterizedpara que pueda reutilizar esta prueba más fácilmente para otras entradas. Alternativamente, si quieres ir exótico, puedes optar por una herramienta de generación de pruebas (y esto ). TestNG tiene mejor soporte para pruebas parametrizadas.

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

Groostav
fuente
2
(con respecto al uso de la excepción esperada) Desde JUnit 4.13, puede usar Assert.assertThrowspara verificar que algunos códigos arrojen una excepción.
MageWind
22

Si tiene la mala suerte de atrapar todos los errores en su código. Puedes hacer estupidamente

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThrowError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}
Ben Tennyson
fuente
1
Solo una pequeña sugerencia, Exception exdebe ser = null;antes de que pueda probarlo.
Denees
44
Esta no es una gran solución. Si el método que no debería arrojar una excepción sí arroja, no recibirá un mensaje de error útil. Simplemente llame al método que no debería generar una excepción y pruebe su valor de retorno (o efectos secundarios, como registrar la excepción). Si luego arroja una excepción inesperadamente, la prueba fallará.
NamshubWriter
3
O simplemente ponga Assert.fail () en la captura, una IMO más fácil y más bonita.
isaac.hazan
Sí estoy de acuerdo con usted. Una forma más es agregar una anotación en la parte superior del método @Test (esperado = InvalidRequestException.class)
Ben Tennyson
Su falta de ortografía es confusa: thisShouldThroughError -> thisShouldThrowError .
Oscar Bravo
7

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

assertAll( () -> foo() )

fuente: JUnit 5 API

razalghul
fuente
7

Aunque esta publicación tiene 6 años, sin embargo, mucho ha cambiado en el mundo Junit. Con Junit5, ahora puedes usar

org.junit.jupiter.api.Assertions.assertDoesNotThrow()

Ex:

public void thisMethodDoesNotThrowException(){
   System.out.println("Hello There");
}

@Test
public void test_thisMethodDoesNotThrowException(){
  org.junit.jupiter.api.Assertions.assertDoesNotThrow(
      ()-> thisMethodDoesNotThrowException()
    );
}

Espero que ayude a las personas que usan una versión más reciente de Junit5

Dinesh Arora
fuente
Desearía que hubiera una manera de especificar una clase de excepción concreta aquí. Tengo que hacer esto dentro de Awaitility's untilAsserted(ThrowingRunnable assertion). El sistema bajo prueba actualmente está lanzando una excepción específica en el ThrowingRunnable que proporciono, pero quiero darle algo de tiempo hasta que deje de hacerlo. Sin embargo, si arrojaría una excepción diferente, me gustaría que la prueba fallara al instante.
Ubeogesh
1

Si desea probar si su objetivo de prueba consume la excepción. Simplemente deje la prueba como (simulacro de colaborador 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 objetivo 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á sobrediseñado si solo desea 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-there -an-easy-way / ) puede ayudar si insiste en tdd en este caso.

Yugang Zhou
fuente
1

Utilice afirmar nulo (...)

@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}
Mike Rapadas
fuente
66
Yo diría que esto es engañoso. El bloque catch nunca se alcanza, por lo que assertNulltampoco se ejecuta. Sin embargo, el lector rápido tiene la impresión de que se hace una afirmación que realmente verifica el caso de no lanzar. En otras palabras: si se alcanza el bloque catch, la excepción siempre es no nula; por lo tanto, se puede reemplazar por un simple fail.
Andreas
engañoso de hecho, ..... pero espera, ... oh ya veo ... assertNull(e)informará que la prueba falló, como se indicó eno puede estar nullen el catchbloque ... Mike, esto es simplemente una programación extraña: - /. .. sí, al menos úsalo fail()como dice Andreas
Julien
1

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

@Rule
public ExpectedException expectedException = ExpectedException.none();
LazerBanana
fuente
ExpectedExceptions se utilizan para afirmar excepciones lanzadas. El código que proporciona es solo para inicializar la regla para que pueda agregar sus requisitos para las aserciones. Este código en sí mismo no agrega ningún valor en absoluto. El javadoc también establece esto: "/ ** * Devuelve una {@linkplain TestRule rule} que no espera que se lance * una excepción (idéntico al comportamiento sin esta regla). * /" Por lo tanto, tendrá exactamente el mismo resultado que sin ella .
Pim Hazebroek
Estoy de acuerdo con usted y no lo usaría de esa manera, pero es posible afirmar que no se ha lanzado ninguna excepción. Si la prueba pasa, debería haber suficiente para decir que no se ha lanzado la excepción, pero, por otro lado, si hay una pregunta, debe ser necesaria. Y rara vez, pero aún a veces, es bueno tenerlo visible. ¿Qué pasa si el código y las circunstancias cambiaron y no tenemos pruebas para algún caso límite en particular?
LazerBanana
Tengo curiosidad por ver cómo afirmarías eso con la excepción esperada. Y sí, si los requisitos cambian y no tiene una prueba para un caso de borde en particular, está atornillado ;-) siempre cubra todos los casos de esquina.
Pim Hazebroek
¿Qué quieres decir? no lo afirmas, lo esperas. En este caso, no espera ninguna excepción. No estoy seguro de qué estás hablando.
LazerBanana
0

Puede que esta no sea la mejor manera, pero definitivamente se asegura de que no se genere una excepción desde el bloque de código que se está probando.

import org.assertj.core.api.Assertions;
import org.junit.Test;

public class AssertionExample {

    @Test
    public void testNoException(){
        assertNoException();
    }    

    private void assertException(){
        Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class);
    }

    private void assertNoException(){
        Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class);
    }

    private void doNotThrowException(){
        //This method will never throw exception
    }
}
MLS
fuente
0

Puede hacerlo utilizando una @Rule y luego llamar al método reportMissingExceptionWithMessage como se muestra a continuación: Este es el código Scala.

ingrese la descripción de la imagen aquí

Crenguta S
fuente
1
private val? ¿Que idioma es este? Claramente no Java; p Y, por favor, no proporcione código como captura de pantalla, no es bienvenido.
Andremoniy
Veo que has mencionado que es Scala, pero decir que se puede hacer fácilmente en Java no es un argumento fuerte, lo siento
Andremoniy
Quité la parte que te molestó. Intentaré reemplazar la imagen también. Todavía no he descubierto cómo agregar código ...
Crenguta S
-1

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

@Test
public void testMyCode() {

    try {
        runMyTestCode();
    } catch (Throwable t) {
        throw new Error("fail!");
    }
}
Rocky Inde
fuente
-1

Puede crear cualquier tipo de sus propias aserciones basadas en aserciones de junit:

static void assertDoesNotThrow(Executable executable) {
    assertDoesNotThrow(executable, "must not throw");
}
static void assertDoesNotThrow(Executable executable, String message) {
    try {
        executable.execute();
    } catch (Throwable err) {
        fail(message);
    }
}

Y prueba:

//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "fail with specific message: facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "Fail: must not trow");

En términos generales, existe la posibilidad de fallar instantáneamente ("bla bla bla") la prueba en cualquier escenario, en cualquier lugar donde tenga sentido. Por ejemplo, úselo en un bloque try / catch para fallar si se arroja algo en el caso de prueba:

try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
//or
try{methodMustNotThrow(1);}catch(Throwable e){Assertions.fail("must not throw");}

Esta es la muestra del método que probamos, suponiendo que tengamos un método que no debe fallar en circunstancias específicas, pero puede fallar:

void methodMustNotThrow(int x) throws Exception{
    if (x == 1) return;
    throw new Exception();
}

El método anterior es una muestra simple. Pero esto funciona para situaciones complejas, donde el fracaso no es tan obvio. Están las importaciones:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.*;
armagedescu
fuente
Hay una opción bastante mejor para verificar que no se haya lanzado una afirmación que no implique la creación de código personalizado. @Rule es uno de ellos
Vargan
@ Vargan He señalado el método para crear sus propias afirmaciones en la forma en que está diseñado por JUnit, especialmente con el fin de crear sus propias afirmaciones. JUnit proporciona que, por diseño, especialmente para ese propósito, cree sus propias reglas, extienda el comportamiento de JUnit con afirmaciones que aún no están implementadas. Porque no todo se implementa en este mundo. Estas afirmaciones funcionan de manera idéntica a la de JUnit en términos de aprobar o reprobar, así como informar fallas.
armagedescu