Devo evitar métodos privados se eu executar TDD?

93

Eu só agora estou aprendendo TDD. No meu entender, métodos privados não podem ser testados e não devem se preocupar porque a API pública fornecerá informações suficientes para verificar a integridade de um objeto.

Eu entendi a OOP por um tempo. No meu entender, métodos privados tornam os objetos mais encapsulados, portanto, mais resistentes a mudanças e erros. Assim, eles devem ser usados por padrão e somente os métodos que importam aos clientes devem ser tornados públicos.

Bem, é possível criar um objeto que tenha apenas métodos particulares e interaja com outros objetos ouvindo seus eventos. Isso seria muito encapsulado, mas completamente não testável.

Além disso, é considerado uma prática ruim adicionar métodos para testar.

Isso significa que o TDD está em desacordo com o encapsulamento? Qual é o equilíbrio adequado? Estou inclinado a tornar a maioria ou todos os meus métodos públicos agora ...

    
por pup 14.02.2012 / 16:58
fonte

12 respostas

43

Prefere testar a interface durante o teste na implementação.

It's my understanding that private methods are untestable

Isso depende do seu ambiente de desenvolvimento, veja abaixo.

[private methods] shouldn't be worried about because the public API will provide enough information for verifying an object's integrity.

É isso mesmo, o TDD se concentra em testar a interface.

Métodos privados são um detalhe de implementação que pode mudar durante qualquer ciclo de re-fatoração. Deve ser possível refatorar sem alterar a interface ou o comportamento caixa preta . Na verdade, isso é parte do benefício do TDD, a facilidade com a qual você pode gerar a confiança de que alterações internas em uma classe não afetarão os usuários dessa classe.

Well, it's possible for me to make an object that has only private methods and interacts with other objects by listening to their events. This would be very encapsulated, but completely untestable.

Mesmo que a classe não tenha métodos públicos, os manipuladores de eventos são interface pública , e é contra essa interface pública que você pode testar.

Como os eventos são a interface, são os eventos que você precisará gerar para testar esse objeto.

Procure usar objetos simulados como a cola para o seu sistema de teste. Deve ser possível criar um objeto simulado simples que gere um evento e capte a mudança de estado resultante (possível por outro objeto simulado do receptor).

Also, it's considered bad practice to add methods for the sake of testing.

Absolutamente, você deve ser muito cauteloso em expor o estado interno.

Does this mean TDD is at odds with encapsulation? What is the appropriate balance?

Absolutamente não.

O TDD não deve alterar a implementação de suas aulas além de talvez simplificá-las (aplicando YAGNI de um ponto anterior).

A prática recomendada com o TDD é idêntica à prática recomendada sem o TDD. Você descobre o motivo, porque está usando a interface durante o desenvolvimento.

I'm inclined to make most or all of my methods public now...

Isso seria jogar o bebê com a água do banho.

Você não deve precisar para tornar todos os métodos públicos para que você possa desenvolver de forma TDD. Veja minhas anotações abaixo para ver se seus métodos privados realmente não podem ser testados.

Uma visão mais detalhada dos métodos privados de teste

Se você precisar testar algum comportamento particular de uma classe, dependendo do idioma / ambiente, você pode ter três opções:

  1. Coloque os testes na aula que você deseja testar.
  2. Coloque os testes em outra classe / arquivo de origem & expor os métodos privados que você deseja testar como métodos públicos.
  3. Use um ambiente de teste que permita manter separado o código de teste e produção e, ainda assim, permitir o acesso de código de teste a métodos privados do código de produção.

Obviamente, a terceira opção é de longe a melhor.

1) Coloque os testes na classe que você quer testar (não ideal)

Armazenar casos de teste na mesma classe / arquivo de origem que o código de produção sob teste é a opção mais simples. Mas sem muitas diretivas ou anotações pré-processador, você acabará com seu código de teste inchando seu código de produção desnecessariamente, e dependendo de como você estruturou seu código, você pode acabar expondo acidentalmente a implementação interna aos usuários daquele código. / p>

2) Exponha os métodos privados que você deseja testar como métodos públicos (realmente não é uma boa ideia)

Como sugerido, essa é uma prática muito ruim, destrói o encapsulamento e exporá a implementação interna aos usuários do código.

3) Use um ambiente de teste melhor (melhor opção, se estiver disponível)

No mundo do Eclipse, 3. pode ser alcançado usando fragmentos . No mundo C #, podemos usar classes parciais . Outros idiomas / ambientes geralmente têm funcionalidade semelhante, você só precisa encontrá-lo.

Assumindo cegamente 1. ou 2. as únicas opções provavelmente resultariam em software de produção inchado com código de teste ou interfaces de classe desagradáveis que lavam sua roupa suja em público. * 8 ')

  • Ao todo - é muito melhor não testar a implementação privada.
por 14.02.2012 / 18:28
fonte
75

Claro que você pode ter métodos privados e, claro, pode testá-los.

Existe alguma maneira de executar o método privado, caso em que você pode testá-lo dessa forma, ou não há nenhuma maneira de fazer o privado executar, caso em que: por que diabos você está tentando testá-lo, basta apagar a maldita coisa!

No seu exemplo:

Well, it's possible for me to make an object that has only private methods and interacts with other objects by listening to their events. This would be very encapsulated, but completely untestable.

Por que isso seria não testável? Se o método for invocado em reação a um evento, basta fazer com que o teste alimente o objeto com um evento apropriado.

Não se trata de não ter métodos privados, mas de não quebrar o encapsulamento. Você pode ter métodos privados, mas deve testá-los por meio da API pública. Se a API pública for baseada em eventos, use eventos.

Para o caso mais comum de métodos auxiliares privados, eles podem ser testados pelos métodos públicos que os chamam. Em particular, desde que você só tem permissão para escrever código para fazer um teste falhar e seus testes estão testando a API pública, todo código novo que você escreve normalmente será público. Os métodos privados só aparecem como resultado de uma Refatoração de método de extração , quando são extraídos de um método público já existente. Mas, nesse caso, o teste original que testa o método público também abrange o método privado, já que o método público chama o método privado.

Portanto, geralmente os métodos privados só aparecem quando são extraídos de métodos públicos já testados e, portanto, já são testados.

    
por 14.02.2012 / 17:19
fonte
25

Quando você cria uma nova classe em seu código, faz isso para responder a alguns requisitos. Os requisitos dizem o que o código deve fazer, não como . Isso facilita entender por que a maioria dos testes acontece no nível de métodos públicos.

Por meio de testes, verificamos se o código faz o que se espera que ele faça , gera exceções apropriadas quando esperado, etc. Não nos importamos com a maneira como o código é implementado pelo desenvolvedor. Embora não nos importemos com a implementação, ou seja, como o código faz o que ele faz, faz sentido evitar o teste de métodos privados.

Quanto a classes de teste que não possuem nenhum método público e interagem com o mundo externo somente através de eventos, você pode testar isso também enviando, através de testes, os eventos e ouvindo a resposta. Por exemplo, se uma classe precisar salvar um arquivo de log toda vez que receber um evento, o teste de unidade enviará o evento e verificará se o arquivo de registro foi gravado.

Por último, mas não menos importante, em alguns casos, é perfeitamente válido testar métodos privados. É por isso que, por exemplo, no .NET, você pode testar não apenas classes públicas, mas também privadas, mesmo que a solução não seja tão simples quanto para métodos públicos.

    
por 14.02.2012 / 17:27
fonte
6

It's my understanding that private methods are untestable

Eu não concordo com essa afirmação, ou eu diria que você não testa métodos privados diretamente . Um método público pode chamar métodos privados diferentes. Talvez o autor quisesse ter métodos "pequenos" e extraído parte do código em um método privado inteligentemente chamado.

Independentemente de como o método público é escrito, seu código de teste deve cobrir todos os caminhos. Se você descobrir após seus testes que uma das instruções da filial (se / switch) em um método privado nunca foi abordada em seus testes, então você tem um problema. Ou você perdeu um caso e a implementação está correta OU a implementação está errada, e esse ramo nunca deveria ter existido de fato.

É por isso que uso Cobertura e NCover muito, para garantir que meu teste de método público também abranja métodos privados. Sinta-se à vontade para escrever bons objetos OO com métodos privados e não deixe o TDD / Testar entrar em seu caminho nesse assunto.

    
por 14.02.2012 / 17:20
fonte
5

Seu exemplo ainda é perfeitamente testável, desde que você use a Injeção de Dependência para fornecer as instâncias com as quais a CUT interage. Então você pode usar uma simulação, gerar os eventos de interesse e, em seguida, observar se a CUT executa ou não as ações corretas em suas dependências.

Por outro lado, se você tiver uma linguagem com bom suporte a eventos, você pode seguir um caminho um pouco diferente. Eu não gosto quando os objetos se inscrevem em eventos em si, em vez disso, têm a fábrica que cria o objeto wire up eventos para os métodos públicos do objeto. É mais fácil de testar e torna visível externamente os tipos de eventos para os quais o CUT precisa ser testado.

    
por 14.02.2012 / 17:23
fonte
5

Você não precisa abandonar o uso de métodos privados. É perfeitamente razoável usá-los, mas, de uma perspectiva de teste, é mais difícil testar diretamente sem quebrar o encapsulamento ou adicionar código específico de teste às suas classes. O truque é minimizar as coisas que você sabe que vão fazer seu intestino se contorcer porque você sente que você sujou seu código.

Estas são as coisas que tenho em mente para tentar obter um equilíbrio viável.

  1. Minimize o número de métodos e propriedades particulares que você usa. A maioria das coisas que você precisa que sua turma faça tende a ser exposta publicamente de qualquer maneira, então pense se você realmente precisa tornar esse método inteligente privado.
  2. Minimize a quantidade de código em seus métodos privados - você realmente deve fazer isso de qualquer maneira - e teste indiretamente onde você pode, por meio do comportamento de outros métodos. Você nunca espera obter 100% de cobertura de teste e, talvez, precise verificar alguns valores por meio do depurador. Usar métodos privados para lançar exceções pode ser facilmente testado indiretamente. As propriedades privadas podem precisar ser testadas manualmente ou por outro método.
  3. Se a verificação indireta ou manual não lhe agradar, adicione um evento protegido e acesse por meio de uma interface para expor algumas das coisas particulares. Isso efetivamente "dobra" as regras de encapsulamento, mas evita a necessidade de realmente enviar o código que executa seus testes. A desvantagem é que isso pode resultar em um pequeno código interno adicional para garantir que o evento seja acionado quando necessário.
  4. Se você acha que um método público não é "seguro" o suficiente, veja se há maneiras de implementar algum tipo de processo de validação em seus métodos para limitar o modo como eles são usados. As chances são de que, enquanto você está pensando nisso, pense em uma maneira melhor de implementar seus métodos ou verá outra classe começando a tomar forma.
  5. Se você tem muitos métodos privados fazendo "coisas" para seus métodos públicos, pode haver uma nova classe esperando para ser extraída. Você pode testar isso diretamente como uma classe separada, mas implementar como um composto de forma privada dentro da classe que o utiliza.

Pense lateralmente. Mantenha suas classes pequenas e seus métodos menores e use muita composição. Parece mais trabalho, mas no final você vai acabar com mais itens testáveis individualmente, seus testes serão mais simples, você terá mais opções para usar simples mocks no lugar de objetos reais, grandes e complexos, esperançosamente bem código factorored e fracamente acoplado, e mais importante você vai dar a si mesmo mais opções. Manter as coisas pequenas tende a economizar seu tempo no final, porque você reduz o número de coisas que você precisa verificar individualmente em cada aula, e você tende a reduzir naturalmente o código espaguete que às vezes pode acontecer quando uma classe fica grande e tem muito comportamento de código interdependente internamente.

    
por 14.02.2012 / 23:16
fonte
3

Well, it's possible for me to make an object that has only private methods and interacts with other objects by listening to their events. This would be very encapsulated, but completely untestable.

Como esse objeto reage a esses eventos? Presumivelmente, ele deve invocar métodos em outros objetos. Você pode testá-lo, verificando se esses métodos são chamados. Chame um objeto falso e então você pode facilmente afirmar que ele faz o que você espera.

A questão é que só queremos testar a interação do objeto com outros objetos. Nós não nos importamos com o que está acontecendo dentro de um objeto. Então não, você não deveria ter mais nenhum método público antes.

    
por 14.02.2012 / 17:21
fonte
3

Eu lutei com esse mesmo problema também. Realmente, a maneira de contornar isso é: Como você espera que o resto do seu programa faça interface com essa classe? Teste sua turma de acordo. Isso forçará você a projetar sua turma com base em como o restante do programa faz interface com ela e, de fato, estimulará o encapsulamento e o bom design de sua turma.

    
por 14.02.2012 / 19:20
fonte
3

Em vez de modificador padrão de uso privado. Então você pode testar esses métodos individualmente, não apenas em conjunto com métodos públicos. Isso requer que seus testes tenham a mesma estrutura de pacotes que seu código principal.

    
por 14.02.2012 / 20:32
fonte
2

Alguns métodos privados geralmente não são um problema. Você acabou de testá-los através da API pública como se o código estivesse embutido em seus métodos públicos. Um excesso de métodos privados pode ser um sinal de fraca coesão. Sua turma deve ter uma responsabilidade coesa, e muitas vezes as pessoas tornam os métodos privados para dar a aparência de coesão onde realmente não existe nenhum.

Por exemplo, você pode ter um manipulador de eventos que faz muitas chamadas de banco de dados em resposta a esses eventos. Como é obviamente uma prática ruim instanciar um manipulador de eventos para fazer chamadas de banco de dados, a tentação é tornar todos os métodos privados de chamadas relacionadas ao banco de dados, quando eles realmente devem ser colocados em uma classe separada.

    
por 14.02.2012 / 17:32
fonte
1

Does this mean TDD is at odds with encapsulation? What is the appropriate balance? I'm inclined to make most or all of my methods public now.

O TDD não está em desacordo com o encapsulamento. Tome o exemplo mais simples de um método ou propriedade getter, dependendo do idioma de sua escolha. Digamos que eu tenha um objeto Customer e queira que ele tenha um campo Id. O primeiro teste que vou escrever é algo que diz algo como "customer_id_initializes_to_zero". Eu defino o getter para lançar uma exceção não implementada e ver o teste falhar. Então, a coisa mais simples que eu posso fazer para passar no teste é fazer com que o getter retorne zero.

De lá, eu faço outros testes, presumivelmente aqueles que envolvem o ID do cliente como um campo funcional real. Em algum momento, eu provavelmente tenho que criar um campo privado que a classe de cliente usa para acompanhar o que deve ser retornado pelo getter. Como exatamente eu acompanho isso? É um simples apoio int? Acompanho uma string e depois a converto em int? Eu acompanho 20 ints e os classifico? O mundo exterior não se importa - e seus testes de TDD não se importam. Esse é um detalhe encapsulado .

Acho que nem sempre é imediatamente óbvio ao iniciar o TDD - você não está testando os métodos internamente - está testando preocupações menos granulares da classe. Então, você não está tentando testar esse método DoSomethingToFoo() instancia uma barra, chama um método, adiciona duas a uma de suas propriedades, etc. Você está testando isso depois de alterar o estado do objeto, algum estado acessor mudou (ou não). Esse é o padrão geral de seus testes: "quando eu faço X para minha classe em teste, posso observar Y posteriormente". Como chegar a Y não é uma preocupação dos testes, e isso é o que é encapsulado e é por isso que o TDD não está em desacordo com o encapsulamento.

    
por 14.02.2012 / 17:54
fonte
1

Evite usar? Não.
Evite começar com ? Sim.

Percebo que você não perguntou se está tudo bem em ter aulas abstratas com o TDD; se você entender como as classes abstratas surgem durante o TDD, o mesmo princípio se aplica também aos métodos privados.

Você não pode testar diretamente métodos em classes abstratas como não pode testar diretamente métodos privados, mas é por isso que você não começa com classes abstratas e métodos privados; você começa com classes concretas e APIs públicas e, depois, refaz a funcionalidade comum.

    
por 15.02.2012 / 00:44
fonte