java - unitarios - teste unitario exception




Como testar que nenhuma exceção é lançada? (9)

Eu sei que uma maneira de fazer isso seria:

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

Existe alguma maneira mais limpa de fazer isso. (Provavelmente usando o @Rule do @Rule ?)



Eu tropecei nisso por causa da regra do SonarQube "squid: S2699": "Adicione pelo menos uma afirmação a este caso de teste."

Eu tive um teste simples cujo único objetivo era passar sem lançar exceções.

Considere este código simples:

public class Printer {

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

Que tipo de asserção pode ser adicionado para testar este método? Claro, você pode fazer um try-catch em torno dele, mas isso é apenas código inchado.

A solução vem do próprio JUnit.

Caso nenhuma exceção seja lançada e você queira ilustrar explicitamente esse comportamento, basta incluir o expected como no exemplo a seguir:

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

Test.None.class é o padrão para o valor esperado.


O JUnit5 adiciona o método assertAll () para este propósito exato.

assertAll( () -> foo() )

fonte: JUnit 5 API


O Java 8 torna isso muito mais fácil e o Kotlin / Scala duplamente.

Podemos escrever uma pequena classe de utilidade

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 }

e então seu código se torna simples:

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

Se você não tem acesso ao Java-8, eu usaria um recurso java antigo: blocos de código arbitrário e um simples comentário

//setup
Component component = new Component();

//act
configure(component);

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

E finalmente, com o kotlin, uma língua pela qual eu recentemente me apaixonei:

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()
}

Embora haja muito espaço para mexer com exatamente como você quer expressar isso, eu sempre fui fã de afirmações fluentes .

A respeito de

Você está se aproximando disso da maneira errada. Apenas teste sua funcionalidade: se uma exceção for lançada, o teste falhará automaticamente. Se nenhuma exceção for lançada, seus testes ficarão verdes.

Isto é correto em princípio, mas incorreto na conclusão.

Java permite exceções para o fluxo de controle. Isso é feito pelo próprio tempo de execução do JRE em APIs como Double.parseDouble por meio de um NumberFormatException e Paths.get por meio de um InvalidPathException .

Tendo em conta que você escreveu um componente que valida Number strings para Double.ParseDouble , talvez usando um Regex, talvez um analisador escrito à mão ou talvez algo que incorpore algumas outras regras de domínio que restrinja o intervalo de um double a algo específico, como melhor para testar este componente? Eu acho que um teste óbvio seria afirmar que, quando a string resultante é analisada, nenhuma exceção é lançada. Eu escreveria esse teste usando o assertDoesNotThrow ou /*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));
}

Eu também encorajaria você a parametrizar este teste na input usando Theories ou Parameterized para que você possa reutilizar mais facilmente este teste para outras entradas. Alternativamente, se você quer ser exótico, você pode usar uma ferramenta de geração de testes (e this ). TestNG tem melhor suporte para testes parametrizados.

O que eu acho particularmente desagradável é a recomendação de usar @Test(expectedException=IllegalArgumentException.class) , essa exceção é perigosamente ampla . Se o seu código muda de tal forma que o construtor do componente sob teste tem if(constructorArgument <= 0) throw IllegalArgumentException() , e seu teste estava fornecendo 0 para esse argumento porque era conveniente - e isso é muito comum, porque bons dados de teste de geração é um problema surpreendentemente difícil--, então seu teste será em barra verde mesmo que não teste nada. Tal teste é pior do que inútil.


Se você é azarado o suficiente para pegar todos os erros no seu código. Você pode estupidamente fazer

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

Se você quiser testar se o seu alvo de teste consome a exceção. Apenas deixe o teste como (colaborador simulado usando jMock2):

@Test
public void consumesAndLogsExceptions() throws Exception {

    context.checking(new Expectations() {
        {
            oneOf(collaborator).doSth();
            will(throwException(new NullPointerException()));
        }
    });

    target.doSth();
 }

O teste passaria se seu destino consumisse a exceção lançada, caso contrário, o teste falharia.

Se você quiser testar sua lógica de consumo de exceção, as coisas ficam mais complexas. Sugiro delegar o consumo a um colaborador que possa ser ridicularizado. Portanto, o teste poderia 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();
 }

Mas às vezes é superprojetado se você quiser apenas registrá-lo. Neste caso, este artigo ( http://java.dzone.com/articles/monitoring-declarative-transac , http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there-an-easy-way/ ) pode ajudar se você insistir em tdd neste caso.


Você está se aproximando disso da maneira errada. Apenas teste sua funcionalidade: se uma exceção for lançada, o teste falhará automaticamente. Se nenhuma exceção for lançada, seus testes ficarão verdes.

Eu notei que essa pergunta atrai interesse de tempos em tempos, então eu vou expandir um pouco.

Fundo para testes unitários

Quando você é um teste de unidade, é importante definir para você mesmo o que você considera uma unidade de trabalho. Basicamente: uma extração de sua base de código que pode ou não incluir vários métodos ou classes que representam uma única peça de funcionalidade.

Ou, como definido em A arte do teste de unidade, 2ª edição por Roy Osherove , página 11:

Um teste de unidade é um código automatizado que chama a unidade de trabalho que está sendo testada e, em seguida, verifica algumas suposições sobre um único resultado final dessa unidade. Um teste de unidade é quase sempre escrito usando uma estrutura de teste de unidade. Pode ser escrito facilmente e é executado rapidamente. É confiável, legível e sustentável. É consistente em seus resultados, desde que o código de produção não tenha mudado.

O que é importante perceber é que uma unidade de trabalho geralmente não é apenas um método, mas no nível básico é um método e depois é encapsulado por outra unidade de trabalho.

Idealmente, você deve ter um método de teste para cada unidade separada de trabalho, para que você possa sempre ver imediatamente onde as coisas estão indo mal. Neste exemplo, há um método básico chamado getUserById() que retornará um usuário e há um total de 3 unidades de trabalho.

A primeira unidade de trabalho deve testar se um usuário válido está sendo retornado ou não no caso de uma entrada válida e inválida.
Quaisquer exceções que estão sendo lançadas pela origem de dados devem ser tratadas aqui: se nenhum usuário estiver presente, deve haver um teste que demonstre que uma exceção é lançada quando o usuário não pode ser encontrado. Uma amostra disso poderia ser a IllegalArgumentException que é capturada com a anotação @Test(expected = IllegalArgumentException.class) .

Depois de ter lidado com todos os seus casos de uso para esta unidade básica de trabalho, você subirá um nível. Aqui você faz exatamente o mesmo, mas você só lida com as exceções que vêm do nível logo abaixo do atual. Isso mantém seu código de teste bem estruturado e permite que você corra rapidamente pela arquitetura para descobrir onde as coisas dão errado, em vez de ter que pular em todos os lugares.

Manipulando uma entrada válida e com falha de testes

Neste ponto, deve ficar claro como vamos lidar com essas exceções. Existem dois tipos de entrada: entrada válida e entrada defeituosa (a entrada é válida no sentido estrito, mas não está correta).

Quando você trabalha com uma entrada válida, você está configurando a expectativa implícita de que qualquer teste que você escreva funcionará.

Essa chamada de método pode ter esta aparência: existingUserById_ShouldReturn_UserObject . Se este método falhar (por exemplo: uma exceção é lançada), então você sabe que algo deu errado e você pode começar a cavar.

Adicionando outro teste ( nonExistingUserById_ShouldThrow_IllegalArgumentException ) que usa a entrada defeituosa e espera uma exceção, você pode ver se o seu método faz o que deve fazer com a entrada incorreta.

TL; DR

Você estava tentando fazer duas coisas em seu teste: verifique se há entradas válidas e incorretas. Ao dividir isso em dois métodos, cada um faz uma coisa, você terá testes muito mais claros e uma visão geral muito melhor de onde as coisas dão errado.

Ao manter em mente a unidade de trabalho em camadas, você também pode reduzir a quantidade de testes necessários para uma camada mais alta na hierarquia, pois não é necessário considerar todos os erros que podem ter ocorrido nas camadas inferiores: camadas abaixo da atual são uma garantia virtual de que suas dependências funcionam e se algo der errado, está na sua camada atual (supondo que as camadas inferiores não apresentem nenhum erro).


Você pode esperar que a exceção não seja lançada criando uma regra.

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

O JUnit 5 (Jupiter) fornece três funções para verificar ausência / presença de exceção:

assertAll​()

Afirma que todos os executables fornecidos
não lance exceções.

assertDoesNotThrow​()

Afirma que a execução do
fornecido executable / supplier
não lança nenhum tipo de exception .

Esta função está disponível
desde o JUnit 5.2.0 (29 de abril de 2018).

assertThrows​()

Afirma que a execução do executable fornecido
lança uma exceção do expectedType
e retorna a exception .

Exemplo

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));
    }

}




junit4