Pergunta

Eu preciso para simular um cenário de teste em que eu chamar o método getBytes() de um objeto String e eu recebo um UnsupportedEncodingException.

Eu tentei conseguir isso usando o seguinte código:

String nonEncodedString = mock(String.class);
when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));

O problema é que quando eu executar o meu caso de teste eu recebo um MockitoException que diz que eu não posso zombar uma classe java.lang.String.

Existe uma maneira de zombar um objeto String usando Mockito ou, alternativamente, uma maneira de fazer o meu objeto String lançar uma UnsupportedEncodingException quando eu chamar o método getBytes?


Aqui estão mais detalhes para ilustrar o problema:

Esta é a classe que eu quero de teste:

public final class A {
    public static String f(String str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}

Esta é minha classe de teste (estou usando JUnit 4 e Mockito):

public class TestA {

    @Test(expected=UnsupportedEncodingException.class)
    public void test(){
        String aString = mock(String.class);
        when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));
        A.f(aString);
    }
}
Foi útil?

Solução

O problema é a classe String em Java é marcada como final, para que você não simulada pode está utilizando frameworks de zombaria tradicionais. De acordo com a Mockito FAQ , esta é uma limitação deste quadro também.

Outras dicas

Como cerca de apenas criar um String com um nome de codificação ruim? Veja

public String(byte bytes[], int offset, int length, String charsetName)

Mocking String é quase certamente uma má idéia.

Se tudo o que você vai fazer em seu bloco de captura é lançar uma exceção de tempo de execução, em seguida, você pode poupar alguns digitando apenas usando um objeto Charset para especificar o nome do conjunto de caracteres.

public final class A{
    public static String f(String str){
        return new String(str.getBytes(Charset.forName("UTF-8")));
    }
}

Desta forma, você não estão a recuperar uma exceção que nunca vai acontecer só porque o compilador diz-lhe para.

Como já foi indicado, você não pode usar Mockito para zombar uma classe final. No entanto, o ponto mais importante é que o teste não é especialmente útil porque está apenas demonstrando que String.getBytes() pode lançar uma exceção, que ele pode, obviamente, fazer. Se você se sentir fortemente sobre a testar esta funcionalidade, eu acho que você poderia adicionar um parâmetro para a codificação para f() e enviar um valor ruim para o teste.

Além disso, você está causando o mesmo problema para o chamador de A.f() porque A é final e f() é estático.

Este artigo pode ser útil em convencer seus colegas de trabalho a ser menos dogmático sobre a cobertura de código 100%: Como a falhar com 100% de cobertura de teste .

Desde a sua documentação, JDave não pode remover os modificadores "finais" de classes carregadas pelo carregador de classe de bootstrap. Isso inclui todas as classes JRE (de java.lang, java.util, etc.).

A ferramenta que o deixa nada simulada é JMockit .

Com JMockit, o teste pode ser escrita como:

import java.io.*;
import org.junit.*;
import mockit.*;

public final class ATest
{
   @Test(expected = UnsupportedOperationException.class)
   public void test() throws Exception
   {
      new Expectations()
      {
         @Mocked("getBytes")
         String aString;

         {
            aString.getBytes(anyString);
            result = new UnsupportedEncodingException("Parsing error.");
         }
      };

      A.f("test");
   }
}

assumindo que a classe completa "A" é a seguinte:

import java.io.*;

public final class A
{
   public static String f(String str)
   {
      try {
         return new String(str.getBytes("UTF-8"));
      }
      catch (UnsupportedEncodingException e) {
         throw new UnsupportedOperationException(e);
      }
   }
}

Eu realmente executado este teste na minha máquina. (Aviso eu envolvi o original exceção verificada em uma exceção de tempo de execução.)

Eu costumava zombeteiro parcial através @Mocked("getBytes") para evitar JMockit de zombando de tudo na classe java.lang.String (imaginem o que poderia causar).

Agora, este teste é realmente desnecessário, porque "UTF-8" é um conjunto de caracteres padrão, que devem ser suportados em todos os JREs. Portanto, em um ambiente de produção do bloco catch nunca será executado.

A "necessidade" ou desejo de cobrir o bloco catch ainda é válido, no entanto. Então, como se livrar do teste sem reduzir o percentual de cobertura? Aqui é a minha idéia: inserir uma linha com assert false; como a primeira instrução dentro do bloco catch, e ter a ferramenta de cobertura de código ignorar todo o bloco catch ao relatar medidas de cobertura. Este é um dos meus "itens TODO" para JMockit Cobertura. 8 ^)

Mockito não pode zombar classes final. JMock, combinada com uma biblioteca de JDave lata. Aqui estão as instruções .

JMock não fazer nada de especial para fins que não contam com a biblioteca JDave a tudo UNFINALIZE na JVM, para que você possa experimentar com usando unfinalizer de JDave e ver se Mockito então zombar-lo classes final.

Você também pode usar extensão Mockito de PowerMock para zombar classes final / métodos, mesmo em classes de sistema como String. No entanto, eu também aconselhamento contra zombando getBytes neste caso e sim tentar configurar sua expectativa para que uma verdadeira é String preenchida com os dados esperados é usado.

Você será testar código que nunca pode ser executado. suporte UTF-8 é necessário para estar em cada máquina virtual Java, consulte http://java.sun.com/javase/6/docs/api/java/nio/charset/Charset.html

É uma exigência do projeto que a unidade testa percentual de cobertura obrigatória, mas maior do que um determinado valor. Para atingir tal percentagem de cobertura os testes devem cobrir o bloco de captura em relação ao UnsupportedEncodingException.

O que é que a meta de cobertura dada? Algumas pessoas diriam que tiro para a cobertura de 100% nem sempre um é boa idéia .

Além disso, isso não é maneira de testar se ou não um bloco catch foi exercida. O caminho certo é escrever um método que faz com que a exceção seja lançada e fazer observação da exceção sendo lançada o critério de sucesso. Você faz isso com anotação @Test do JUnit, adicionando o valor "esperado":

@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
   new ArrayList<Object>().get(1);
}

Você já tentou passar uma charsetName inválido para getBytes (String)?

Você poderia implementar um método auxiliar para obter o charsetName, e substituir esse método dentro de seu teste para um valor absurdo.

Talvez A.F (String) deve ser A.F (CharSequence) em vez. Você pode zombar um CharSequence.

Se você pode usar JMockit, olhada resposta Rogério.

Se e somente se o seu objetivo é obter cobertura de código, mas não realmente simular o que falta UTF-8 seria semelhante em tempo de execução que você pode fazer o seguinte (e que você não pode ou não quer usar JMockit):

public static String f(String str){
    return f(str, "UTF-8");
}

// package private for example
static String f(String str, String charsetName){
    try {
        return new String(str.getBytes(charsetName));
    } catch (UnsupportedEncodingException e) {
        throw new IllegalArgumentException("Unsupported encoding: " + charsetName, e);
    }
}

public class TestA {

    @Test(expected=IllegalArgumentException.class)
    public void testInvalid(){
        A.f(str, "This is not the encoding you are looking for!");
    }

    @Test
    public void testNormal(){
        // TODO do the normal tests with the method taking only 1 parameter
    }
}

Você pode mudar o seu método de tomar o CharSequence de interface:

public final class A {
    public static String f(CharSequence str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}

Dessa forma, você ainda pode passar em String, mas você pode zombar de qualquer maneira que você gosta.

Se você tem um bloco de código que nunca pode realmente ser executado, e uma exigência gerencial para ter cobertura de teste de 100%, então algo vai ter que mudar.

O que você poderia fazer é fazer o personagem que codifica uma variável de membro, e adicionar um construtor pacote-privadas para sua classe que permite que você passá-lo. Em seu teste de unidade, você poderia chamar o novo construtor, com um valor absurdo para a codificação de caracteres.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top