Como faço para testar uma função privada ou uma classe que possui métodos, campos ou classes internas privadas?

StackOverflow https://stackoverflow.com/questions/34571

  •  09-06-2019
  •  | 
  •  

Pergunta

Como faço para testar a unidade (usando xUnit) de uma classe que possui métodos privados internos, campos ou classes aninhadas?Ou uma função que se torna privada por ter ligação interna (static em C/C++) ou está em um ambiente privado (anônimo) espaço para nome?

Parece ruim alterar o modificador de acesso de um método ou função apenas para poder executar um teste.

Foi útil?

Solução

Atualizar:

Cerca de 10 anos depois, talvez a melhor maneira de testar um método privado, ou qualquer membro inacessível, seja através @Jailbreak de Múltiplo estrutura.

@Jailbreak Foo foo = new Foo();
// Direct, *type-safe* access to *all* foo's members
foo.privateMethod(x, y, z);
foo.privateField = value;

Dessa forma, seu código permanece seguro e legível.Sem compromissos de design, sem superexposição de métodos e campos para fins de testes.

Se você tem algum legado Java aplicativo, e você não tem permissão para alterar a visibilidade de seus métodos, a melhor maneira de testar métodos privados é usar reflexão.

Internamente, estamos usando ajudantes para obter/definir private e private static variáveis, bem como invocar private e private static métodos.Os padrões a seguir permitirão que você faça praticamente qualquer coisa relacionada aos métodos e campos privados.Claro, você não pode mudar private static final variáveis ​​através da reflexão.

Method method = TargetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

E para campos:

Field field = TargetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Notas:
1. TargetClass.getDeclaredMethod(methodName, argClasses) permite que você analise private métodos.A mesma coisa se aplica getDeclaredField.
2.O setAccessible(true) é obrigado a brincar com soldados rasos.

Outras dicas

A melhor maneira de testar um método privado é através de outro método público.Se isso não puder ser feito, uma das seguintes condições será verdadeira:

  1. O método privado é um código morto
  2. Há um cheiro de design perto da classe que você está testando
  3. O método que você está tentando testar não deve ser privado

Quando tenho métodos privados em uma classe que são suficientemente complicados a ponto de sentir a necessidade de testar os métodos privados diretamente, isso é um cheiro de código:minha aula é muito complicada.

Minha abordagem usual para resolver esses problemas é criar uma nova classe que contenha as partes interessantes.Freqüentemente, esse método e os campos com os quais ele interage, e talvez outro método ou dois, possam ser extraídos para uma nova classe.

A nova classe expõe esses métodos como 'públicos', portanto eles são acessíveis para testes unitários.As classes novas e antigas agora são mais simples que a classe original, o que é ótimo para mim (preciso manter as coisas simples ou me perderei!).

Observe que não estou sugerindo que as pessoas criem aulas sem usar o cérebro!O objetivo aqui é usar as forças dos testes unitários para ajudá-lo a encontrar novas classes boas.

Eu tenho usado reflexão fazer isso para Java no passado e, na minha opinião, foi um grande erro.

A rigor, você deveria não estar escrevendo testes unitários que testam diretamente métodos privados.O que você deve be testing é o contrato público que a classe tem com outros objetos;você nunca deve testar diretamente os componentes internos de um objeto.Se outro desenvolvedor quiser fazer uma pequena alteração interna na classe, que não afete o contrato público da classe, ele deverá modificar seu teste baseado em reflexão para garantir que funcione.Se você fizer isso repetidamente ao longo de um projeto, os testes de unidade deixarão de ser uma medida útil da integridade do código e começarão a se tornar um obstáculo ao desenvolvimento e um aborrecimento para a equipe de desenvolvimento.

O que eu recomendo fazer é usar uma ferramenta de cobertura de código como a Cobertura, para garantir que os testes de unidade que você escreve forneçam uma cobertura decente do código em métodos privados.Dessa forma, você testa indiretamente o que os métodos privados estão fazendo e mantém um maior nível de agilidade.

Deste artigo: Testando Métodos Privados com JUnit e SuiteRunner (Bill Venners), você tem basicamente 4 opções:

  • Não teste métodos privados.
  • Dê acesso ao pacote de métodos.
  • Use uma classe de teste aninhada.
  • Use reflexão.

Geralmente, um teste de unidade tem como objetivo exercitar a interface pública de uma classe ou unidade.Portanto, métodos privados são detalhes de implementação que você não esperaria testar explicitamente.

Apenas dois exemplos de onde eu gostaria de testar um método privado:

  1. Rotinas de descriptografia - Eu não gostaria de torná -los visíveis a ninguém para ver apenas para testar, alguém que alguém possa usá -los para descriptografar.Mas eles são intrínsecos ao código, complicados e precisam sempre funcionar (a exceção óbvia é a reflexão que pode ser usada para ver até métodos privados na maioria dos casos, quando SecurityManager não está configurado para evitar isso).
  2. Criando um SDK para consumo comunitário.Aqui, o público assume um significado totalmente diferente, pois esse é o código que o mundo inteiro pode ver (não apenas interno ao meu aplicativo).Coloquei código em métodos privados se não quiser que os usuários do SDK o vejam - não vejo isso como cheiro de código, apenas como funciona a programação do SDK.Mas é claro que ainda preciso testar meus métodos privados, e eles são onde a funcionalidade do meu SDK realmente vive.

Entendo a ideia de testar apenas o “contrato”.Mas não vejo que alguém possa defender o não teste do código - sua milhagem pode variar.

Portanto, minha compensação envolve complicar os JUnits com reflexão, em vez de comprometer minha segurança e SDK.

Os métodos privados são chamados por um método público, portanto as entradas para seus métodos públicos também devem testar métodos privados chamados por esses métodos públicos.Quando um método público falha, isso pode ser uma falha no método privado.

Outra abordagem que usei foi alterar um método privado para empacotar private ou protected e complementá-lo com o @VisibleForTesting anotação da biblioteca Google Guava.

Isso dirá a qualquer pessoa que use esse método para tomar cuidado e não acessá-lo diretamente, mesmo em um pacote.Além disso, uma classe de teste não precisa estar no mesmo pacote fisicamente, mas no mesmo pacote sob o teste pasta.

Por exemplo, se um método a ser testado estiver em src/main/java/mypackage/MyClass.java então sua chamada de teste deve ser feita src/test/java/mypackage/MyClassTest.java.Dessa forma, você terá acesso ao método de teste em sua classe de teste.

Para testar código legado com classes grandes e peculiares, muitas vezes é muito útil poder testar o método privado (ou público) que estou escrevendo agora mesmo.

Eu uso o junitx.util.PrivateAccessor-pacote para Java.Muitas frases úteis para acessar métodos e campos privados.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

Tendo experimentado o Cem Catikkas solução usando reflexão para Java, devo dizer que a solução dele foi mais elegante do que descrevi aqui.No entanto, se você estiver procurando uma alternativa ao uso de reflexão e tiver acesso à fonte que está testando, esta ainda será uma opção.

Existe um mérito possível em testar métodos privados de uma classe, particularmente com desenvolvimento orientado a testes, onde você gostaria de projetar pequenos testes antes de escrever qualquer código.

Criar um teste com acesso a membros e métodos privados pode testar áreas de código que são difíceis de atingir especificamente com acesso apenas a métodos públicos.Se um método público tiver várias etapas envolvidas, ele poderá consistir em vários métodos privados, que poderão então ser testados individualmente.

Vantagens:

  • Pode testar uma granularidade mais fina

Desvantagens:

  • O código de teste deve residir no mesmo arquivo que o código -fonte, que pode ser mais difícil de manter
  • Da mesma forma com os arquivos de saída .class, eles devem permanecer no mesmo pacote declarado no código-fonte

No entanto, se testes contínuos exigirem este método, pode ser um sinal de que os métodos privados devem ser extraídos, os quais poderiam ser testados da forma tradicional e pública.

Aqui está um exemplo complicado de como isso funcionaria:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

A classe interna seria compilada para ClassToTest$StaticInnerTest.

Veja também: Dica Java 106:Classes internas estáticas para diversão e lucro

Como outros já disseram...não teste métodos privados diretamente.Aqui estão alguns pensamentos:

  1. Mantenha todos os métodos pequenos e focados (fáceis de testar, fáceis de descobrir o que está errado)
  2. Use ferramentas de cobertura de código.Eu gosto Cobertura (ah, feliz dia, parece que uma nova versão foi lançada!)

Execute a cobertura de código nos testes de unidade.Se você perceber que os métodos não foram totalmente testados, adicione testes para aumentar a cobertura.Procure obter 100% de cobertura de código, mas saiba que provavelmente não conseguirá.

Os métodos privados são consumidos pelos públicos.Caso contrário, eles são código morto.É por isso que você testa o método público, afirmando os resultados esperados do método público e, portanto, os métodos privados que ele consome.

O teste de métodos privados deve ser testado por depuração antes de executar seus testes de unidade em métodos públicos.

Eles também podem ser depurados usando desenvolvimento orientado a testes, depurando seus testes de unidade até que todas as suas asserções sejam atendidas.

Pessoalmente acredito que é melhor criar classes usando TDD;criando os stubs de método público e, em seguida, gerando testes de unidade com todos as asserções definidas antecipadamente, portanto, o resultado esperado do método é determinado antes de você codificá-lo.Dessa forma, você não segue o caminho errado ao fazer com que as asserções do teste de unidade se ajustem aos resultados.Sua classe fica robusta e atende aos requisitos quando todos os seus testes de unidade são aprovados.

No Estrutura Primavera você pode testar métodos privados usando este método:

ReflectionTestUtils.invokeMethod()

Por exemplo:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");

Se estiver usando Spring, ReflectionTestUtils fornece algumas ferramentas úteis que ajudam aqui com o mínimo de esforço.Por exemplo, para configurar um mock em um membro privado sem ser forçado a adicionar um setter público indesejável:

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);

Se você está tentando testar um código existente que está relutante ou incapaz de alterar, a reflexão é uma boa escolha.

Se o design da classe ainda for flexível e você tiver um método privado complicado que gostaria de testar separadamente, sugiro que você o coloque em uma classe separada e teste essa classe separadamente.Isso não precisa alterar a interface pública da classe original;ele pode criar internamente uma instância da classe auxiliar e chamar o método auxiliar.

Se quiser testar condições de erro difíceis provenientes do método auxiliar, você pode dar um passo adiante.Extraia uma interface da classe auxiliar, adicione um getter e um setter públicos à classe original para injetar a classe auxiliar (usada por meio de sua interface) e, em seguida, injete uma versão simulada da classe auxiliar na classe original para testar como a classe original responde às exceções do ajudante.Essa abordagem também é útil se você quiser testar a classe original sem testar também a classe auxiliar.

Testar métodos privados quebra o encapsulamento da sua classe porque toda vez que você altera a implementação interna você quebra o código do cliente (neste caso, os testes).

Portanto, não teste métodos privados.

A resposta de Página de perguntas frequentes do JUnit.org:

Mas se você precisar...

Se você estiver usando o JDK 1.3 ou superior, poderá usar a reflexão para subverter o mecanismo de controle de acesso com o auxílio do Acesso Privilegiado.Para obter detalhes sobre como usá-lo, leia Este artigo.

Se você estiver usando o JDK 1.6 ou superior e anote seus testes com @Test, você pode usar Dp4j para injetar reflexão em seus métodos de teste.Para detalhes sobre como usá -lo, consulte este script de teste.

P.S.Sou o principal contribuidor Dp4j, perguntar meu Se precisar de ajuda.:)

Se você deseja testar métodos privados de um aplicativo legado onde não é possível alterar o código, uma opção para Java é jMockit, o que permitirá criar simulações para um objeto mesmo quando eles forem privados da classe.

Costumo não testar métodos privados.Aí reside a loucura.Pessoalmente, acredito que você só deve testar suas interfaces expostas publicamente (e isso inclui métodos internos e protegidos).

Se você estiver usando JUnit, dê uma olhada em complementos junit.Tem a capacidade de ignorar o modelo de segurança Java e acessar métodos e atributos privados.

Um método privado só deve ser acessado dentro da mesma classe.Portanto, não há como testar um método “privado” de uma classe alvo de qualquer classe de teste.Uma saída é que você pode realizar testes unitários manualmente ou alterar seu método de “privado” para “protegido”.

E então um método protegido só pode ser acessado dentro do mesmo pacote onde a classe está definida.Portanto, testar um método protegido de uma classe alvo significa que precisamos definir sua classe de teste no mesmo pacote que a classe alvo.

Se todos os itens acima não atenderem às suas necessidades, use o caminho da reflexão para acessar o método privado.

Eu sugiro que você refatore um pouco seu código.Quando você precisa começar a pensar em usar reflexão ou outro tipo de coisa, apenas para testar seu código, algo está errado com seu código.

Você mencionou diferentes tipos de problemas.Vamos começar com campos privados.No caso de campos privados, eu teria adicionado um novo construtor e injetado campos nele.Em vez disso:

public class ClassToTest {

    private final String first = "first";
    private final List<String> second = new ArrayList<>();
    ...
}

Eu teria usado isso:

public class ClassToTest {

    private final String first;
    private final List<String> second;

    public ClassToTest() {
        this("first", new ArrayList<>());
    }

    public ClassToTest(final String first, final List<String> second) {
        this.first = first;
        this.second = second;
    }
    ...
}

Isso não será um problema, mesmo com algum código legado.O código antigo usará um construtor vazio e, se você me perguntar, o código refatorado parecerá mais limpo e você poderá injetar os valores necessários no teste sem reflexão.

Agora sobre métodos privados.Na minha experiência pessoal, quando você precisa criar um stub de um método privado para teste, esse método não tem nada a ver nessa classe.Um padrão comum, nesse caso, seria enrolar dentro de uma interface, como Callable e então você passa essa interface também no construtor (com aquele truque de múltiplos construtores):

public ClassToTest() {
    this(...);
}

public ClassToTest(final Callable<T> privateMethodLogic) {
    this.privateMethodLogic = privateMethodLogic;
}

Principalmente tudo o que escrevi parece ser um padrão de injeção de dependência.Na minha experiência pessoal, é muito útil durante os testes, e acho que esse tipo de código é mais limpo e mais fácil de manter.Eu diria o mesmo sobre classes aninhadas.Se uma classe aninhada contiver lógica pesada, seria melhor se você a movesse como uma classe privada de pacote e a injetasse em uma classe que precisasse dela.

Existem também vários outros padrões de design que usei ao refatorar e manter o código legado, mas tudo depende dos casos do seu código para testar.Usar reflexão principalmente não é um problema, mas quando você tem um aplicativo corporativo que é fortemente testado e os testes são executados antes de cada implantação, tudo fica muito lento (é simplesmente irritante e não gosto desse tipo de coisa).

Também existe injeção de setter, mas eu não recomendaria usá-la.É melhor ficar com um construtor e inicializar tudo quando for realmente necessário, deixando a possibilidade de injetar as dependências necessárias.

Como muitos sugeriram acima, uma boa maneira é testá-los por meio de suas interfaces públicas.

Se você fizer isso, é uma boa ideia usar uma ferramenta de cobertura de código (como Emma) para ver se seus métodos privados estão de fato sendo executados a partir de seus testes.

Aqui está minha função genérica para testar campos privados:

protected <F> F getPrivateField(String fieldName, Object obj)
    throws NoSuchFieldException, IllegalAccessException {
    Field field =
        obj.getClass().getDeclaredField(fieldName);

    field.setAccessible(true);
    return (F)field.get(obj);
}

Veja abaixo um exemplo;

A seguinte instrução de importação deve ser adicionada:

import org.powermock.reflect.Whitebox;

Agora você pode passar diretamente o objeto que possui o método privado, o nome do método a ser chamado e parâmetros adicionais conforme abaixo.

Whitebox.invokeMethod(obj, "privateMethod", "param1");

Hoje, enviei uma biblioteca Java para ajudar a testar métodos e campos privados.Ele foi projetado com o Android em mente, mas pode realmente ser usado para qualquer projeto Java.

Se você obteve algum código com métodos, campos ou construtores privados, você pode usar Caixa Limitada.Ele faz exatamente o que você está procurando.Abaixo está um exemplo de teste que acessa dois campos privados de uma atividade Android para testá-la:

@UiThreadTest
public void testCompute() {

    // Given
    boundBoxOfMainActivity = new BoundBoxOfMainActivity(getActivity());

    // When
    boundBoxOfMainActivity.boundBox_getButtonMain().performClick();

    // Then
    assertEquals("42", boundBoxOfMainActivity.boundBox_getTextViewMain().getText());
}

Caixa Limitada facilita o teste de campos, métodos e construtores privados/protegidos.Você pode até acessar coisas ocultas por herança.Na verdade, o BoundBox quebra o encapsulamento.Isso lhe dará acesso a tudo isso através da reflexão, MAS tudo é verificado em tempo de compilação.

É ideal para testar algum código legado.Use-o com cuidado.;)

https://github.com/stephanenicolas/boundbox

Primeiro, vou descartar esta questão:Por que seus membros privados precisam de testes isolados?Eles são tão complexos, proporcionando comportamentos tão complicados que exigem testes fora da superfície pública?É um teste de unidade, não um teste de 'linha de código'.Não se preocupe com as pequenas coisas.

Se eles forem tão grandes, grandes o suficiente para que cada um desses membros privados seja uma 'unidade' grande em complexidade - considere refatorar esses membros privados fora desta classe.

Se a refatoração for inadequada ou inviável, você pode usar o padrão de estratégia para substituir o acesso a essas funções/classes de membros privadas quando estiver em teste de unidade?No teste de unidade, a estratégia forneceria validação adicional, mas nas compilações de lançamento seria uma simples passagem.

Recentemente tive esse problema e escrevi uma pequena ferramenta, chamada Gazua, que evita os problemas de uso explícito da API de reflexão Java, dois exemplos:

Métodos de chamada, por ex. private void method(String s) - por reflexão Java

Method method = targetClass.getDeclaredMethod("method", String.class);
method.setAccessible(true);
return method.invoke(targetObject, "mystring");

Métodos de chamada, por ex. private void method(String s) - por Picklock

interface Accessible {
  void method(String s);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.method("mystring");

Definir campos, por ex. private BigInteger amount; - por reflexão Java

Field field = targetClass.getDeclaredField("amount");
field.setAccessible(true);
field.set(object, BigInteger.valueOf(42));

Definir campos, por ex. private BigInteger amount; - por Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

...
Accessible a = ObjectAccess.unlock(targetObject).features(Accessible.class);
a.setAmount(BigInteger.valueOf(42));

Para Java eu ​​usaria reflexão, já que não gosto da ideia de alterar o acesso a um pacote no método declarado apenas para fins de teste.No entanto, normalmente apenas testo os métodos públicos, o que também deve garantir que os métodos privados estejam funcionando corretamente.

você não pode usar reflexão para obter métodos privados de fora da classe proprietária, o modificador privado também afeta a reflexão

Isso não é verdade.Você certamente pode, como mencionado em Resposta de Cem Catikkas.

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