Como você testar métodos particulares?

184

Estou trabalhando em um projeto java. Eu sou novo no teste de unidade. Qual é a melhor maneira de testar métodos privados em classes java?

Vinoth Kumar CM
fonte
11
Verifique esta pergunta no StackOverflow. Algumas técnicas são mencionadas e discutidas. Qual é a melhor maneira de testar métodos privados?
Quíron
34
Minha opinião sempre foi que métodos privados não precisam ser testados, pois você deve testar o que está disponível. Um método público. Se você não pode quebrar o método público, isso realmente importa o que os métodos privados estão fazendo?
Rig
11
Os métodos público e privado devem ser testados. Portanto, um driver de teste geralmente precisa estar dentro da classe que ele testa. como este .
overexchange
2
@Rig - +1 - você poderá invocar todo o comportamento necessário de um método privado a partir de seus métodos públicos - se não conseguir, a funcionalidade nunca poderá ser invocada de qualquer maneira, portanto, não faz sentido testá-la.
James Anderson
Use a @Jailbreakpartir da estrutura do Manifold para acessar diretamente métodos privados. Dessa forma, seu código de teste permanece seguro e legível. Acima de tudo, não há comprometimento do projeto, nem métodos e campos de superexposição para fins de teste.
Scott

Respostas:

239

Geralmente, você não realiza testes particulares de unidade diretamente. Como eles são privados, considere-os um detalhe de implementação. Ninguém nunca ligará para um deles e espera que funcione de uma maneira particular.

Em vez disso, você deve testar sua interface pública. Se os métodos que chamam seus métodos particulares estiverem funcionando conforme o esperado, você supõe, por extensão, que seus métodos particulares estão funcionando corretamente.

Adam Lear
fonte
39
+1 bilhão. E se um método privado nunca for chamado, não faça o teste de unidade, exclua-o!
9788 Steven A. Lowe
246
Discordo. Às vezes, um método privado é apenas um detalhe de implementação, mas ainda é complexo o suficiente para justificar o teste, para garantir que funcione corretamente. A interface pública pode estar oferecendo um nível de abstração muito alto para escrever um teste que visa diretamente esse algoritmo específico. Nem sempre é possível fatorá-lo em uma classe separada, devido aos dados compartilhados. Nesse caso, eu diria que não há problema em testar um método privado.
quant_dev
10
Claro exclua-o. O único motivo possível para não excluir uma função que você não está usando é se você acha que precisa dela no futuro e, mesmo assim, deve excluí-la, mas observe a função em seus logs de confirmação ou, pelo menos, comente-a para que as pessoas sabem que são coisas extras que podem ignorar.
jhocking
38
@quant_dev: Às vezes, uma classe precisa ser refatorada em várias outras classes. Seus dados compartilhados também podem ser colocados em uma classe separada. Digamos, uma classe de "contexto". Em seguida, suas duas novas classes podem se referir à classe de contexto para seus dados compartilhados. Isso pode parecer irracional, mas se seus métodos particulares são complexos o suficiente para exigir testes individuais, é um cheiro de código que indica que o gráfico de objetos precisa se tornar um pouco mais granular.
Phil
43
Esta resposta NÃO responde à pergunta. A questão é como os métodos privados devem ser testados, e não se eles devem ser testados. Se um método privado deve ser testado em unidade é uma questão interessante e digna de debate, mas não aqui . A resposta apropriada é adicionar um comentário na pergunta, informando que testar métodos privados pode não ser uma boa idéia e fornecer um link para uma pergunta separada que abordaria o assunto mais profundamente.
precisa saber é o seguinte
118

Em geral, eu evitaria isso. Se o seu método privado é tão complexo que precisa de um teste de unidade separado, geralmente significa que ele merecia sua própria classe. Isso pode encorajá-lo a escrevê-lo de uma maneira reutilizável. Você deve testar a nova classe e chamar a interface pública dela na sua classe antiga.

Por outro lado, às vezes fatorar os detalhes da implementação em classes separadas leva a classes com interfaces complexas, muitos dados passando entre a antiga e a nova classe ou a um design que pode parecer bom do ponto de vista da OOP, mas não corresponder às intuições provenientes do domínio do problema (por exemplo, dividir um modelo de precificação em duas partes, apenas para evitar testar métodos particulares, não é muito intuitivo e pode levar a problemas mais tarde ao manter / estender o código). Você não quer ter "classes gêmeas" que sempre são alteradas juntas.

Quando confrontado com uma escolha entre encapsulamento e testabilidade, prefiro ir para o segundo. É mais importante ter o código correto (ou seja, produzir a saída correta) do que um bom design de POO que não funcione corretamente, porque não foi testado adequadamente. Em Java, você pode simplesmente acessar o método "padrão" e colocar o teste de unidade no mesmo pacote. Os testes de unidade são simplesmente parte do pacote que você está desenvolvendo e não há problema em ter uma dependência entre os testes e o código que está sendo testado. Isso significa que, quando você altera a implementação, pode ser necessário alterar seus testes, mas tudo bem - cada alteração na implementação requer um novo teste do código, e se os testes precisam ser modificados para isso, basta fazer isto.

Em geral, uma classe pode estar oferecendo mais de uma interface. Existe uma interface para os usuários e uma interface para os mantenedores. O segundo pode expor mais para garantir que o código seja testado adequadamente. Não precisa ser um teste de unidade em um método privado - pode ser, por exemplo, registro em log. O registro também "quebra o encapsulamento", mas ainda o fazemos, porque é muito útil.

quant_dev
fonte
9
+1 ponto interessante. No final, trata-se de capacidade de manutenção, não de aderência às "regras" da boa OOP.
Phil
9
+1 Concordo plenamente com a testabilidade sobre o encapsulamento.
Richard
31

O teste de métodos privados dependeria de sua complexidade; alguns métodos privados de uma linha realmente não justificariam o esforço extra de teste (isso também pode ser dito de métodos públicos), mas alguns métodos privados podem ser tão complexos quanto os métodos públicos e difíceis de testar através da interface pública.

Minha técnica preferida é tornar o pacote de método privado privado, o que permitirá o acesso a um teste de unidade no mesmo pacote, mas ele ainda será encapsulado de todos os outros códigos. Isso dará a vantagem de testar diretamente a lógica do método privado, em vez de precisar confiar em um teste de método público para cobrir todas as partes da lógica (possivelmente) complexa.

Se isso estiver associado à anotação @VisibleForTesting na biblioteca do Google Guava, você está claramente marcando este método privado de pacote como visível apenas para teste e, como tal, não deve ser chamado por nenhuma outra classe.

Os opositores dessa técnica argumentam que isso quebrará o encapsulamento e abrirá métodos privados para codificar no mesmo pacote. Embora eu concorde que isso quebra o encapsulamento e abra o código privado para outras classes, eu argumento que testar lógica complexa é mais importante que o encapsulamento estrito e não usar métodos privados de pacote que são claramente marcados como visíveis apenas para teste devem ser de responsabilidade dos desenvolvedores usando e alterando a base de código.

Método privado antes do teste:

private int add(int a, int b){
    return a + b;
}

Método particular do pacote pronto para teste:

@VisibleForTesting
int add(int a, int b){
    return a + b;
}

Nota: Colocar testes no mesmo pacote não é equivalente a colocá-los na mesma pasta física. Separar o código principal e o código de teste em estruturas de pastas físicas separadas é uma boa prática em geral, mas essa técnica funcionará desde que as classes sejam definidas como no mesmo pacote.

Richard
fonte
14

Se você não pode usar APIs externas ou não deseja, ainda pode usar a API JDK padrão pura para acessar métodos privados usando reflexão. Aqui está um exemplo

MyObject obj = new MyObject();
Method privateMethod = MyObject.class.getDeclaredMethod("getFoo", null);
privateMethod.setAccessible(true);
String returnValue = (String) privateMethod.invoke(obj, null);
System.out.println("returnValue = " + returnValue);

Verifique o Java Tutorial http://docs.oracle.com/javase/tutorial/reflect/ ou a API Java http://docs.oracle.com/javase/7/docs/api/java/lang/reflect/package-summary. html para mais informações.

Como @kij citou em sua resposta, há momentos em que uma solução simples usando reflexão é realmente boa para testar um método privado.

Gustavo Coelho
fonte
9

Caso de teste de unidade significa testar a unidade de código. Isso não significa testar a interface, porque se você estiver testando a interface, isso não significa que você está testando a unidade de código. Torna-se uma espécie de teste de caixa preta. Além disso, é melhor encontrar problemas no menor nível de unidade do que determinar os problemas no nível da interface e depois tentar depurar o componente que não estava funcionando. Portanto, o caso de teste de unidade deve ser testado independentemente de seu escopo. A seguir, é uma maneira de testar métodos particulares.

Se você estiver usando java, poderá usar o jmockit, que fornece o Deencapsulation.invoke para chamar qualquer método privado da classe em teste. Ele usa reflexão para chamá-lo eventualmente, mas fornece um bom invólucro ao seu redor. ( https://code.google.com/p/jmockit/ )

agrawalankur
fonte
Como isso responde à pergunta?
mosquito
@gnat, A pergunta era: Qual é a melhor maneira de testar métodos privados em classes java? E o meu primeiro ponto responde à pergunta.
Agrawalankur #
o seu primeiro ponto apenas anuncia uma ferramenta, mas não explica por que você acredita que é melhor do que alternativas, como por exemplo, testando métodos privados indiretamente como foi sugerido e explicado em cima resposta
mosquito
O jmockit mudou e a antiga página oficial aponta para um artigo sobre "Urina sintética e xixi artificial". A propósito, o que ainda está relacionado a zombar de coisas, mas não é relevante aqui :) A nova página oficial é: jmockit.github.io
Guillaume
8

Primeiro de tudo, como outros autores sugeriram: pense duas vezes se você realmente precisa testar o método privado. E se, ...

No .NET, você pode convertê-lo no método "Interno" e tornar o pacote " InternalVisible " no seu projeto de teste de unidade.

Em Java, você pode escrever os próprios testes na classe a ser testada e seus métodos de teste também devem poder chamar métodos privados. Eu realmente não tenho grande experiência em Java, portanto essa provavelmente não é a melhor prática.

Obrigado.

Budda
fonte
7

Eu costumo fazer esses métodos protegidos. Digamos que sua turma esteja em:

src/main/java/you/Class.java

Você pode criar uma classe de teste como:

src/test/java/you/TestClass.java

Agora você tem acesso a métodos protegidos e pode testá-los unitariamente (JUnit ou TestNG realmente não importa), mas mantém esses métodos longe dos chamadores que você não queria.

Observe que isso espera uma árvore de fontes no estilo maven.

Gyorgyabraham
fonte
3

Se você realmente precisa testar o método privado, com Java, quero dizer, você pode usar fest asserte / ou fest reflect. Ele usa reflexão.

Importe a biblioteca com o maven (determinadas versões não são as mais recentes que eu acho) ou importe-a diretamente no seu caminho de classe:

<dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-assert</artifactId>
     <version>1.4</version>
    </dependency>

    <dependency>
     <groupId>org.easytesting</groupId>
     <artifactId>fest-reflect</artifactId>
    <version>1.2</version>
</dependency>

Por exemplo, se você tiver uma classe chamada 'MyClass' com um método privado chamado 'myPrivateMethod', que usa uma String como parâmetro e atualiza seu valor para 'this is cool testing!', Você pode executar o seguinte teste de junção:

import static org.fest.reflect.core.Reflection.method;
...

MyClass objectToTest;

@Before
public void setUp(){
   objectToTest = new MyClass();
}

@Test
public void testPrivateMethod(){
   // GIVEN
   String myOriginalString = "toto";
   // WHEN
   method("myPrivateMethod").withParameterTypes(String.class).in(objectToTest).invoke(myOriginalString);
   // THEN
   Assert.assertEquals("this is cool testing !", myOriginalString);
}

Essa biblioteca também permite que você substitua quaisquer propriedades de bean (não importa se são particulares e nenhum setter é gravado) por um mock, e usá-lo com o Mockito ou qualquer outra estrutura de mock é muito legal. A única coisa que você precisa saber no momento (não sei se isso será melhor nas próximas versões) é o nome do campo / método de destino que você deseja manipular e sua assinatura.

kij
fonte
2
Embora a reflexão permita testar métodos particulares, não é bom para detectar o uso deles. Se você alterar o nome de um método privado, isso interromperá seu teste.
27412 Richard Richard
11
O teste será interrompido sim, mas esse é um problema menor do meu ponto de vista. É claro que concordo totalmente com a sua explicação abaixo sobre a visibilidade do pacote (com classes de testes em pastas separadas, mas com os mesmos pacotes), mas às vezes você não realmente tem a escolha. Por exemplo, se você tem uma missão muito curta em uma empresa que não aplica métodos "bons" (classes de teste que não estão no mesmo pacote etc.), se não tiver tempo para refatorar o código existente no qual está trabalhando / testing, essa ainda é uma boa alternativa.
27712
2

O que normalmente faço em C # é tornar meus métodos protegidos e não particulares. É um modificador de acesso um pouco menos privado, mas oculta o método de todas as classes que não herdam da classe em teste.

public class classUnderTest
{
   //this would normally be private
   protected void methodToTest()
   {
     //some code here
   }
}

Qualquer classe que não herda diretamente de classUnderTest não tem idéia de que methodToTest existe. No meu código de teste, posso criar uma classe de teste especial que estende e fornece acesso a esse método ...

class TestingClass : classUnderTest
{
   public void methodToTest()
   {
     //this returns the method i would like to test
     return base.methodToTest();
   }
}

Essa classe existe apenas no meu projeto de teste. Seu único objetivo é fornecer acesso a esse método único. Permite-me acessar lugares que a maioria das outras classes não possui.

astronauta
fonte
4
protectedfaz parte da API pública e incorre nas mesmas limitações (nunca pode ser alterado, deve ser documentado, ...). Em C #, você pode usar internaljunto com InternalsVisibleTo.
CodesInChaos
1

Você pode testar métodos privados facilmente se colocar seus testes de unidade em uma classe interna na classe que está testando. Usando TestNG, seus testes de unidade devem ser classes internas estáticas públicas anotadas com @Test, assim:

@Test
public static class UserEditorTest {
    public void test_private_method() {
       assertEquals(new UserEditor().somePrivateMethod(), "success");
    }
}

Por ser uma classe interna, o método privado pode ser chamado.

Meus testes são executados no maven e ele encontra automaticamente esses casos de teste. Se você quiser apenas testar uma classe, pode fazer

$ mvn test -Dtest=*UserEditorTest

Fonte: http://www.ninthavenue.com.au/how-to-unit-test-private-methods

Roger Keays
fonte
4
Você está sugerindo a inclusão de código de teste (e classes internas adicionais) no código real do pacote implantado?
11
A classe de teste é uma classe interna da classe que você deseja testar. Se você não deseja que essas classes internas sejam implantadas, basta excluir os arquivos * Test.class do seu pacote.
Roger Keays
11
Eu não amo essa opção, mas para muitos é provavelmente uma solução válida, que não vale a pena.
Bill K
@RogerKeays: Tecnicamente, quando você faz uma implantação, como você remove essas "classes internas de teste"? Por favor, não diga que você os corta à mão.
gyorgyabraham
Eu não os removo. Sim. Eu implanto código que nunca é executado em tempo de execução. Isto é realmente ruim.
Roger Keays