Como testo uma função privada ou uma classe que possui métodos, campos ou classes internas?

2728

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

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

Raedwald
fonte
257
Melhor maneira de testar um método particular não é testá-lo diretamente
Surya
26
Consulte o artigo Testando métodos privados com JUnit e SuiteRunner .
Mouna Cheikhna
383
Discordo. Um método (público) longo ou difícil de compreender precisa ser refatorado. Seria tolice não testar os métodos pequenos (privados) que você obtém, em vez de apenas o método público.
Michael Piefel 25/10/11
181
Não testar nenhum método apenas porque sua visibilidade é estúpida. Mesmo o teste de unidade deve ser o menor pedaço de código e, se você testar apenas métodos públicos, nunca terá certeza agora de onde ocorre o erro - esse método ou outro.
Dainius
126
Você precisa testar a funcionalidade da classe , não sua implementação . Quer testar os métodos particulares? Teste os métodos públicos que os chamam. Se a funcionalidade que a classe oferece é testada exaustivamente, seus componentes internos demonstraram ser corretos e confiáveis; você não precisa testar as condições internas. Os testes devem manter a dissociação das classes testadas.
amigos estão dizendo sobre calimar41

Respostas:

1640

Atualizar:

Cerca de 10 anos depois, talvez a melhor maneira de testar um método privado, ou qualquer membro inacessível, seja via estrutura @Jailbreakdo Manifold .

@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 permanecerá seguro e legível. Sem comprometimentos no projeto, sem métodos e campos de superexposição para fins de teste.

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

Internamente, estamos usando ajudantes para obter / definir privatee private staticvariáveis, além de invocar privatee private staticmétodos. Os seguintes padrões permitem fazer praticamente qualquer coisa relacionada aos métodos e campos privados. Obviamente, você não pode alterar private static finalvariá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 examinar privatemétodos. O mesmo se aplica getDeclaredField.
2. setAccessible(true)É necessário brincar com os soldados.

Cem Catikkas
fonte
350
Útil se você não conhece a API, talvez, mas se estiver tendo que testar métodos privados dessa maneira, há algo no seu design. Como outro pôster diz que o teste de unidade deve testar o contrato da turma: se o contrato for muito amplo e instancia muito do sistema, o design deve ser tratado.
andygavin
21
Muito útil. Usando isso, é importante ter em mente que falharia mal se os testes fossem executados após a ofuscação.
Rick Minerich
20
O código de exemplo não funcionou para mim, mas isso fez thigs mais clara: java2s.com/Tutorial/Java/0125__Reflection/...
Rob
35
Muito melhor do que usar a reflexão diretamente seria usar alguma biblioteca para ela, como o Powermock .
Michael Piefel 25/10/11
25
É por isso que o Design Orientado a Testes é útil. Isso ajuda a descobrir o que precisa ser exposto para validar o comportamento.
Thorbjørn Ravn Andersen
621

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 é 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
Trumpi
fonte
6
Além disso, nunca chegamos perto de 100% de cobertura de código, então por que não concentrar seu tempo fazendo testes de qualidade nos métodos que os clientes realmente usarão diretamente.
Grinch
30
@grinch Spot on. A única coisa que você ganha com o teste de métodos particulares é a depuração de informações, e é para isso que servem os depuradores. Se seus testes do contrato da turma tiverem cobertura total, você terá todas as informações necessárias. Métodos privados são um detalhe de implementação . Se você testá-los, precisará alterá-los sempre que sua implementação for alterada, mesmo que o contrato não o faça. No ciclo de vida completo do software, isso provavelmente custará muito mais do que o benefício que ele oferece.
Erik Madsen
9
@AlexWien, você está certo. Cobertura não era o termo certo. O que eu deveria ter dito era "se seus testes do contrato da turma abrangem todas as contribuições significativas e todos os estados significativos". Isso, como você salientou, pode ser inviável testar através da interface pública do código, ou indiretamente, conforme você se refere a ele. Se esse for o caso de algum código que você está testando, eu estaria inclinado a pensar: (1) o contrato é muito generoso (por exemplo, muitos parâmetros no método) ou (2) o contrato é muito vago (por exemplo, comportamento do método varia muito com o estado da classe). Eu consideraria os dois cheiros de design.
Erik Madsen
16
Quase todos os métodos particulares não devem ser testados diretamente (para reduzir os custos de manutenção). Exceção: métodos científicos podem ter formas como f (a (b (c (x), d (y))), a (e (x, z)), b (f (x, y, z), z)) onde a, b, c, d, e ef são expressões terrivelmente complicadas, mas são inúteis fora dessa fórmula única. Algumas funções (pense MD5) são difíceis de inverter, por isso pode ser difícil (NP-Hard) escolher parâmetros que cubram totalmente o espaço comportamental. É mais fácil testar a, b, c, d, e, f independentemente. Torná-los públicos ou privados de pacotes mentiras de que são reutilizáveis. Solução: torne-os privados, mas teste-os.
epónimo
4
@ Eponymous Estou um pouco dividido neste. O purista orientado a objetos em mim diria que você deve usar uma abordagem orientada a objetos em vez de métodos privados estáticos, permitindo testar através de métodos de instância pública. Mas, novamente, em um arcabouço científico, a sobrecarga de memória é inteiramente provável que seja uma preocupação que, acredito, defende a abordagem estática.
Erik Madsen
323

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

Minha abordagem usual para resolver esses problemas é provocar uma nova classe que contém os bits interessantes. Frequentemente, esse método e os campos com os quais 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', para que sejam acessíveis para testes de unidade. As classes novas e antigas agora são mais simples que a classe original, o que é ótimo para mim (eu preciso manter as coisas simples ou me perco!).

Note que não estou sugerindo que as pessoas criem classes sem usar o cérebro! O objetivo aqui é usar as forças do teste de unidade para ajudá-lo a encontrar boas novas classes.

Jay Bazuzi
fonte
24
A questão era como testar métodos privados. Você diz que faria uma nova classe para isso (e acrescentaria muito mais complexidade) e depois sugeriria não criar nova classe. Então, como testar métodos privados?
Dainius
27
@ Dainius: não sugiro criar uma nova classe apenas para que você possa testar esse método. Sugiro que escrever testes possa ajudá-lo a melhorar seu design: bons designs são fáceis de testar.
Jay Bazuzi
13
Mas você concorda que um bom OOD exporia (tornaria público) apenas métodos necessários para que essa classe / objeto funcione corretamente? todos os outros devem ser privados / protegidos. Portanto, em alguns métodos privados, haverá alguma lógica, e o teste da IMO desses métodos melhorará apenas a qualidade do software. É claro que concordo que, se um pedaço de código é complexo, ele deve ser dividido para separar métodos / classe.
Dainius
6
@ Danius: Adicionando uma nova classe, na maioria dos casos, reduz a complexidade. Apenas meça. A testabilidade em alguns casos luta contra o OOD. A abordagem moderna é a favor da testabilidade.
21313 AlexWien
5
É exatamente assim que você deve usar o feedback de seus testes para impulsionar e aprimorar seu design! Escute seus testes. se eles são difíceis de escrever, isso significa algo importante sobre o seu código!
Pnschofield
289

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

A rigor, você não deve escrever testes de unidade que testem diretamente métodos privados. O que você deve testar é o contrato público que a classe possui com outros objetos; você nunca deve testar diretamente as partes internas de um objeto. Se outro desenvolvedor quiser fazer uma pequena alteração interna na classe, o que não afeta o contrato público da classe, ele deverá modificar seu teste baseado em reflexão para garantir que funcione. Se você fizer isso repetidamente em 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 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ê indiretamente testa o que os métodos privados estão fazendo e mantém um nível mais alto de agilidade.

Jon
fonte
76
+1 a isso. Na minha opinião, é a melhor resposta para a pergunta. Ao testar métodos particulares, você está testando a implementação. Isso anula o objetivo do teste de unidade, que é testar as entradas / saídas de um contrato de classe. Um teste deve unicamente sabe o suficiente sobre a implementação de zombar os métodos que insta suas dependências. Nada mais. Se você não pode alterar sua implementação sem precisar alterar um teste - é provável que sua estratégia de teste seja ruim.
Colin M
10
@ Colin M Isso não é realmente o que ele está pedindo;) Deixe-o decidir, você não conhece o projeto.
Aaron Marcus
2
Na verdade não é verdade. Pode ser necessário muito esforço para testar uma pequena parte do método privado através do método público que o utiliza. O método público poderia exigir configuração significativa antes de chegar à linha que chama o método privado
ACV
1
Concordo. Você deve testar se o contrato for cumprido. Você não deve testar como isso é feito. Se o contato for realizado sem atingir 100% de cobertura de código (em métodos particulares), isso pode ser um código morto ou inútil.
Wuppi
207

Neste artigo: Testando métodos privados com JUnit e SuiteRunner (Bill Venners), você basicamente tem 4 opções:

  1. Não teste métodos particulares.
  2. Conceda acesso ao pacote de métodos.
  3. Use uma classe de teste aninhada.
  4. Use reflexão.
Iker Jimenez
fonte
@ JimmyT., Depende de quem é o "código de produção". Eu chamaria o código produzido para aplicativos estacionados na VPN cujos usuários de destino são administradores de sistemas como código de produção.
Pacerier
@Pacerier O que você quer dizer?
Jimmy T.
1
5ª opção, como mencionado acima, está testando o método público que chama o método privado? Corrigir?
user2441441
1
@LorenzoLerate Venho lutando com isso porque estou desenvolvendo o incorporado e gostaria que meu software fosse o menor possível. Restrições de tamanho e desempenho são a única razão pela qual consigo pensar.
Eu realmente gosto do teste usando a reflexão: Aqui o método padrão método = targetClass.getDeclaredMethod (methodName, argClasses); method.setAccessible (true); return method.invoke (targetObject, argObjects);
Aguid 27/10
127

Geralmente, um teste de unidade destina-se a exercer 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.

John Channing
fonte
16
Essa é a melhor resposta da IMO, ou como costuma ser dito, testar o comportamento, não os métodos. O teste de unidade não substitui as métricas de código-fonte, as ferramentas de análise de código estático e as análises de código. Se os métodos privados são tão complexos que precisam separar testes, é provável que ele precise ser refatorado, e não mais testes.
Dan Haynes
14
então não escreva métodos particulares, apenas crie 10 outras classes pequenas?
Navalha #
1
Não, significa basicamente que você pode testar a funcionalidade dos métodos privados usando o método público que os chama.
Akshat Sharda
67

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

  1. Rotinas de descriptografia - eu não gostaria de torná-las visíveis para qualquer pessoa ver apenas para fins de teste; caso contrário, alguém pode usá-las 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 exibir até métodos privados na maioria dos casos, quando SecurityManagernão está configurada para evitar isso).
  2. Criando um SDK para consumo da comunidade. 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). Coloco o código em métodos privados se não quero 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 particulares, e eles são onde a funcionalidade do meu SDK realmente vive.

Entendo a ideia de apenas testar o "contrato". Mas não vejo como alguém possa advogar na verdade não testar o código - sua milhagem pode variar.

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

Richard Le Mesurier
fonte
5
Embora você nunca deva tornar público um método apenas para testá-lo, privatenão é a única alternativa. Nenhum modificador de acesso é package privatee significa que você pode testá-lo enquanto o seu teste estiver no mesmo pacote.
ngreen
1
Eu estava comentando sua resposta, ponto 1 em particular, não o OP. Não há necessidade de tornar um método privado apenas porque você não deseja que ele seja público.
N /
1
@ threen true thx - eu estava com preguiça com a palavra "público". Atualizei a resposta para incluir público, protegido, padrão (e fazer menção à reflexão). O ponto que eu estava tentando enfatizar é que há boas razões para que algum código seja secreto, mas isso não deve nos impedir de testá-lo.
Richard Le Mesurier
2
Obrigado por dar exemplos reais para todos. A maioria das respostas é boa, mas do ponto de vista teórico. No mundo real, precisamos ocultar a implementação do contrato e ainda precisamos testá-la. Lendo tudo isso, acho que talvez esse seja um nível totalmente novo de teste, com um nome diferente
Z. Khullah
1
Mais um exemplo - analisadores HTML de rastreador . Ter que analisar uma página html inteira em objetos de domínio requer uma tonelada de lógica detalhada para validar pequenas partes da estrutura. Faz sentido dividir isso em métodos e chamá-lo de um método público, mas não faz sentido que todos os métodos menores que o compõem sejam públicos, nem faz muito sentido criar 10 classes por página HTML.
Nether
59

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.

Thomas Owens
fonte
Fato verídico. O problema é quando a implementação é mais complicada, como se um método público chamasse vários métodos privados. Qual deles falhou? Ou se é um método particular complicado, que não podem ser expostos nem transferido para uma nova classe
Z. Khullah
Você já mencionou por que o método privado deve ser testado. Você disse "então poderia ser", então não tem certeza. Na IMO, se um método deve ser testado é ortogonal ao seu nível de acesso.
Wei Qiu
39

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

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

Por exemplo, se um método a ser testado estiver presente src/main/java/mypackage/MyClass.java, sua chamada de teste deverá ser efetuada src/test/java/mypackage/MyClassTest.java. Dessa forma, você tem acesso ao método de teste em sua classe de teste.

Peter Mortensen
fonte
1
Eu não sabia disso, é interessante, ainda acho que se você precisar desse tipo de anotação, terá problemas de design.
Seb
2
Para mim, é o mesmo que dizer que, em vez de dar uma chave única ao seu guarda de incêndio para testar a broca de incêndio, você deixa a porta da frente destrancada com uma placa na porta dizendo - "destrancado para teste - se você não é de nossa empresa, por favor não entre ".
P. Jeremy Krieg
@FrJeremyKrieg - o que isso significa?
MasterJoe2 10/03
1
@ MasterJoe2: o objetivo de um teste de incêndio é melhorar a segurança do seu prédio contra o risco de incêndio. Mas você precisa dar ao diretor acesso ao seu prédio. Se você deixar permanentemente a porta da frente destrancada, aumenta o risco de acesso não autorizado e a torna menos segura. O mesmo se aplica ao padrão VisibleForTesting - você aumenta o risco de acesso não autorizado, a fim de reduzir o risco de "incêndio". É melhor conceder acesso único para teste apenas quando você precisar (por exemplo, usar reflexão) em vez de deixá-lo permanentemente desbloqueado (não privado).
P. Jeremy Krieg
34

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

Eu uso o pacote junitx.util.PrivateAccessor para Java. Muitas linhas de ajuda úteis para acessar métodos e campos particulares.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);
phareim
fonte
2
Certifique-se de baixar o pacote inteiro JUnit-addons ( sourceforge.net/projects/junit-addons ), não o projeto PrivateAccessor recomendado pelo Source Forge.
precisa saber é o seguinte
2
E certifique-se de que, ao usar a Classcomo primeiro parâmetro nesses métodos, você esteja acessando apenas staticmembros.
Skype.heliou
31

No Spring Framework, você pode testar métodos privados usando este método:

ReflectionTestUtils.invokeMethod()

Por exemplo:

ReflectionTestUtils.invokeMethod(TestClazz, "createTest", "input data");
Mikhail
fonte
A solução mais limpa e concisa até agora, mas apenas se você estiver usando o Spring.
Denis Nikolaenko
28

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

Existe mérito possível ao testar métodos particulares de uma classe, principalmente no desenvolvimento orientado a testes , no qual você deseja projetar pequenos testes antes de escrever qualquer código.

A criação de um teste com acesso a membros e métodos privados pode testar áreas de código 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 particulares, que poderão ser testados individualmente.

Vantagens:

  • Pode testar com uma granularidade mais fina

Desvantagens:

  • O código de teste deve residir no mesmo arquivo que o código-fonte, o 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 o teste contínuo exigir esse método, pode ser um sinal de que os métodos privados devem ser extraídos, o que pode ser testado da maneira 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

Grundlefleck
fonte
26

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ácil de testar, fácil de encontrar o que está errado)
  2. Use ferramentas de cobertura de código. Gosto de Cobertura (feliz dia, parece que uma nova versão foi lançada!)

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

TofuBeer
fonte
Disponível para cobertura de código. Não importa que tipo de lógica esteja no método privado, você ainda está invocando essa lógica por meio de um método público. Uma ferramenta de cobertura de código pode mostrar quais partes são cobertas pelo teste; portanto, você pode ver se seu método particular foi testado.
coolcfan
Eu já vi classes em que o único método público é principal [], e elas exibem a GUI e conectam o banco de dados e alguns servidores Web em todo o mundo. Fácil de dizer "não teste indiretamente" ...
Audrius Meskauskas
26

Métodos privados são consumidos por métodos 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 particulares 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 todas as asserções definidas anteriormente, para que o resultado esperado do método seja determinado antes da codificação. Dessa forma, você não segue o caminho errado de fazer com que as asserções de teste de unidade se ajustem aos resultados. Sua classe é robusta e atende aos requisitos quando todos os seus testes de unidade passam.

Antony Booth
fonte
2
Embora isso seja verdade, pode levar a alguns testes muito complicados - é um padrão melhor testar um único método de cada vez (teste de unidade), em vez de um grupo inteiro deles. Não é uma sugestão terrível, mas acho que há respostas melhores aqui - esse deve ser o último recurso.
Bill K
24

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

ReflectionTestUtils.setField(theClass, "theUnsettableField", theMockObject);
Steve Chambers
fonte
24

Se você está tentando testar o código existente que não está disposto a mudar, a reflexão é uma boa escolha.

Se o design da classe ainda for flexível e você tiver um método privado complicado que você deseja testar separadamente, sugiro que você o puxe para 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 você deseja testar condições de erro difíceis provenientes do método auxiliar, pode dar um passo adiante. Extraia uma interface da classe auxiliar, adicione um getter e setter público à 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 a exceções do ajudante. Essa abordagem também é útil se você quiser testar a classe original sem também testar a classe auxiliar.

Don Kirkby
fonte
19

O teste de métodos particulares interrompe o encapsulamento de sua classe, pois toda vez que você altera a implementação interna, o código do cliente é interrompido (nesse caso, os testes).

Portanto, não teste métodos particulares.

Peter Mortensen
fonte
4
teste de unidade e código src são um par. Se você alterar o src, talvez seja necessário alterar o teste de unidade. Esse é o sentido do teste de junção. Eles devem garantir que tudo funcione como antes. e tudo bem se eles quebrarem, se você alterar o código.
21313 AlexWien
1
Não concorde que o teste de unidade deva mudar se o código for alterado. Se você receber uma ordem para adicionar funcionalidade a uma classe sem alterar a funcionalidade existente da classe, os testes de unidade existentes deverão passar mesmo após a alteração do código. Como Peter menciona, os testes de unidade devem testar a interface, não como é feito internamente. No Test Driven Development, os testes de unidade são criados antes da escrita do código, para se concentrar na interface da classe, não na maneira como ele é resolvido internamente.
precisa saber é o seguinte
16

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

Mas se você deve ...

Se você estiver usando o JDK 1.3 ou superior, poderá usar a reflexão para subverter o mecanismo de controle de acesso com a ajuda do PrivilegedAccessor . Para detalhes sobre como usá-lo, leia este artigo .

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

PS: Eu sou o principal colaborador do Dp4j , pergunte- me se você precisar de ajuda. :)

simpatico
fonte
16

Se você deseja testar métodos particulares de um aplicativo herdado em que não pode alterar o código, uma opção para Java é o jMockit , que permitirá criar simulações para um objeto, mesmo quando elas são privadas para a classe.

Will Sargent
fonte
4
Como parte da biblioteca jmockit, você tem acesso à classe Deencapsulation, que facilita o teste de métodos privados: Deencapsulation.invoke(instance, "privateMethod", param1, param2);
Domenic D.
Eu uso essa abordagem o tempo todo. Muito útil
MedicineMan
14

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

bmurphy1976
fonte
14

Se você estiver usando o JUnit, consulte os junit-addons . Ele tem a capacidade de ignorar o modelo de segurança Java e acessar métodos e atributos privados.

Joe
fonte
13

Eu sugiro que você refatorar seu código um pouco. Quando você começa 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 particulares. No caso de campos particulares, 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 stubar um método privado para teste, esse método não tem nada a ver nessa classe. Um padrão comum, nesse caso, seria envolvê- lo em uma interface, como Callablee você passará nessa interface também no construtor (com esse truque de vários 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, é realmente útil durante os testes, e acho que esse tipo de código é mais limpo e será 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 tivesse movido como uma classe privada de pacote e a injetado em uma classe que precisa.

Também existem vários outros padrões de design que eu usei ao refatorar e manter o código legado, mas tudo depende dos casos do seu código para testar. Usar a 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 realmente lento (é irritante e eu não gosto desse tipo de coisa).

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

GROX13
fonte
1
Não concordo com a ClassToTest(Callable)injeção. Isso torna ClassToTestmais complicado. Mantenha simples. Além disso, isso exige que outra pessoa conte ClassToTestalgo que ClassToTestdeve ser o chefe. Há um lugar para injetar lógica, mas não é isso. Você acabou de tornar a classe mais difícil de manter, não mais fácil.
Loduwijk 9/0317
Além disso, é preferível que seu método de teste Xnão aumente a Xcomplexidade a ponto de causar mais problemas ... e, portanto, exigir testes adicionais ... que se você tiver implementado de uma maneira que possa causar mais problemas .. . (este não é um loops infinitos; cada iteração é provável menor do que o anterior, mas ainda irritante)
Loduwijk
2
Você poderia explicar por que isso tornaria a aula ClassToTestmais difícil de manter? Na verdade, ele facilita a manutenção do aplicativo. O que você sugere para criar uma nova classe para cada valor diferente que você precisa firste na 'segunda' variável?
GROX13 9/0317
1
O método de teste não aumenta a complexidade. É apenas a sua turma que está mal escrita, tão ruim que não pode ser testada.
GROX13
Mais difícil de manter, porque a classe é mais complicada. class A{ A(){} f(){} }é mais simples que class A { Logic f; A(logic_f) } class B { g() { new A( logic_f) } }. Se isso não fosse verdade, e se fosse mais fácil manter a lógica de uma classe como argumentos construtores, passaríamos toda a lógica como argumentos construtores. Eu simplesmente não vejo como você pode reivindicar class B{ g() { new A( you_dont_know_your_own_logic_but_I_do ) } }que torna A mais fácil de manter. Há casos em que injetáveis como este sentido marcas, mas eu não ver o seu exemplo como "mais fácil de manter"
Loduwijk
12

Um método privado deve ser acessado apenas na mesma classe. Portanto, não há como testar um método "privado" de uma classe de destino de qualquer classe de teste. Uma saída é que você pode executar o teste de unidade manualmente ou pode mudar seu método de "privado" para "protegido".

E, em seguida, um método protegido pode ser acessado apenas dentro do mesmo pacote em que a classe está definida. Portanto, testar um método protegido de uma classe de destino significa que precisamos definir sua classe de teste no mesmo pacote que a classe de destino.

Se todas as opções acima não atenderem aos seus requisitos, use a maneira de reflexão para acessar o método privado.

loknath
fonte
Você está misturando "protegido" com "amigável". Um método protegido pode ser acessado apenas por uma classe cujos objetos são atribuíveis à classe de destino (ou seja, subclasses).
Sayo Oladeji
1
Na verdade, não existe "Amigável" em Java, o termo é "Pacote" e é indicado pela falta de um modificador privado / público / protegido. Eu teria corrigido esta resposta, mas já existe uma muito boa que já diz isso - por isso, recomendo excluí-la.
Bill K
Eu quase derrotei, pensando o tempo todo "Esta resposta é simplesmente errada". até chegar à última frase, que contradiz o resto da resposta. A última frase deveria ter sido a primeira.
Loduwijk 9/03/19
11

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.

Grevas de Darren
fonte
Você não deve testar indiretamente! Não apenas tocando via cobertura; teste para que o resultado esperado seja entregue!
AlexWien
11

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

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);
}
Yuli Reiri
fonte
10

Por favor, veja abaixo um exemplo;

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

import org.powermock.reflect.Whitebox;

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

Whitebox.invokeMethod(obj, "privateMethod", "param1");
Olcay Tarazan
fonte
10

Hoje, instalei 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 particulares, pode usar o BoundBox . Faz exatamente o que você está procurando. Aqui está um exemplo de teste que acessa dois campos particulares de uma atividade do 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());
}

O BoundBox facilita o teste de campos, métodos e construtores privados / protegidos. Você pode até acessar coisas ocultas por herança. De fato, o BoundBox quebra o encapsulamento. Isso lhe dará acesso a tudo isso através da reflexão, MAS tudo será verificado no momento da compilação.

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

https://github.com/stephanenicolas/boundbox

Snicolas
fonte
1
Apenas tentei, BoundBox é uma solução simples e elegante!
forhas,
9

Primeiro, jogarei essa pergunta fora: Por que seus membros privados precisam de testes isolados? Eles são tão complexos, fornecendo comportamentos tão complicados que exigem testes separados 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 são tão grandes, grandes o suficiente para que esses membros privados sejam uma 'unidade' grande em complexidade - considere refatorar esses membros privados dessa 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-membro privadas / classes-membro quando estiver em teste de unidade? Sob teste de unidade, a estratégia forneceria validação adicional, mas nas versões lançadas seria uma passagem simples.

Aaron
fonte
3
Porque muitas vezes uma parte específica do código de um método público é refatorada para um método privado interno e é realmente a parte crítica da lógica que você pode ter errado. Você quer testar este independentemente do método público
oxbow_lakes
Mesmo o código mais curto, às vezes sem teste de unidade, não está correto. Apenas tente calcular a diferença entre dois ângulos geográficos. 4 linhas de código, e a maioria não fará isso corretamente na primeira tentativa. Tais métodos precisam de teste de unidade, porque formam a base de um código confiável. (Ans tal código útil pode ser público, também, menos útil protegida
AlexWien
8

Recentemente, tive esse problema e escrevi uma pequena ferramenta, chamada Picklock , que evita os problemas de usar explicitamente a API de reflexão Java, dois exemplos:

Métodos de chamada, por exemplo 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 exemplo 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 exemplo private BigInteger amount;- por reflexão Java

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

Definir campos, por exemplo private BigInteger amount;- por Picklock

interface Accessible {
  void setAmount(BigInteger amount);
}

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

PowerMockito é feito para isso. Usar dependência maven

<dependency>
    <groupId>org.powermock</groupId>
    <artifactId>powermock-mockito-release-full</artifactId>
    <version>1.6.4</version>
</dependency>

Então você pode fazer

import org.powermock.reflect.Whitebox;
...
MyClass sut = new MyClass();
SomeType rval = Whitebox.invokeMethod(sut, "myPrivateMethod", params, moreParams);
Victor Grazi
fonte