Devo evitar métodos privados se eu executar TDD?


100

Só agora estou aprendendo TDD. Entendo que os métodos privados não são testáveis ​​e não devem se preocupar, porque a API pública fornecerá informações suficientes para verificar a integridade de um objeto.

Eu entendi OOP por um tempo. Entendo que métodos privados tornam os objetos mais encapsulados, portanto, mais resistentes a alterações e erros. Portanto, eles devem ser usados ​​por padrão e apenas os métodos que importam para os clientes devem ser tornados públicos.

Bem, é possível criar um objeto que possua 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 saldo apropriado? Estou inclinado a tornar a maioria ou todos os meus métodos públicos agora ...


9
Más práticas e realidade na indústria de software são animais diferentes. Uma situação ideal costuma ser uma realidade distorcida no mundo dos negócios. Faça o que faz sentido e cumpra-o em todo o aplicativo. Eu prefiro ter uma prática ruim no lugar do que o sabor do mês espalhado pelo aplicativo.
Aaron McIver

10
"métodos privados não são testáveis"? Qual língua? Em alguns idiomas, é inconveniente. Em outras línguas, é perfeitamente simples. Além disso, você está dizendo que o princípio de design do encapsulamento sempre deve ser implementado com muitos métodos particulares? Isso parece um pouco extremo. Algumas linguagens ainda não possuem métodos particulares, ainda parecem ter designs bem encapsulados.
31512 S.Lott

"Entendo que os métodos privados tornam os objetos mais encapsulados, portanto, mais resistentes a alterações e erros. Portanto, eles devem ser usados ​​por padrão e apenas os métodos que importam para os clientes devem ser tornados públicos". Isso me parece um ponto de vista contrário do que o TDD está tentando alcançar. TDD é uma metodologia de desenvolvimento que leva você a criar um design simples, viável e aberto a alterações. Olhar "de privado" e "apenas divulgar ..." é invertido completamente. Esquecer que existe um método privado para adotar o TDD. Mais tarde, faça-o conforme necessário; como parte da refatoração.
ervoso


Então, @gnat, você acha que isso deve ser encerrado como uma duplicata da pergunta que surgiu da minha resposta a essa pergunta? * 8 ')
Mark Booth

Respostas:


50

Prefira testar à interface do que testar na implementação.

Entendo que métodos privados não podem ser testados

Isso depende do seu ambiente de desenvolvimento, veja abaixo.

[métodos privados] não devem se preocupar porque a API pública fornecerá informações suficientes para verificar a integridade de um objeto.

É 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 re-fatorar sem alterar a interface ou o comportamento da caixa preta . De fato, isso faz parte do benefício do TDD, a facilidade com a qual você pode gerar a confiança de que mudanças internas a uma classe não afetarão os usuários dessa classe.

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

Mesmo se a classe não tem métodos públicos, é manipuladores de eventos são é interface pública , e seu 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).

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

Absolutamente, você deve ter muito cuidado com a exposição do estado interno.

Isso significa que o TDD está em desacordo com o encapsulamento? Qual é o saldo apropriado?

Absolutamente não.

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

As melhores práticas com TDD são idênticas às melhores práticas sem TDD, basta descobrir o motivo mais cedo, porque você está usando a interface enquanto a desenvolve.

Estou inclinado a tornar a maioria ou todos os meus métodos públicos agora ...

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

Você não precisa tornar todos os métodos públicos, para poder desenvolver de maneira TDD. Veja minhas anotações abaixo para ver se seus métodos privados são realmente intratáveis.

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

Se você absolutamente deve testar alguns comportamentos particulares de uma classe, dependendo do idioma / ambiente, você pode ter três opções:

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

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

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

Armazenar casos de teste no mesmo arquivo de classe / origem que o código de produção em teste é a opção mais simples. Mas sem muitas diretivas ou anotações pré-processador, você terminará com o código de teste inchando seu código de produção desnecessariamente e, dependendo de como você estruturou seu código, poderá acabar expondo acidentalmente a implementação interna para os usuários desse código.

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

Conforme sugerido, essa é uma prática muito ruim, destrói o encapsulamento e expõe 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 Eclipse, 3. pode ser alcançado usando fragmentos . No mundo C #, podemos usar classes parciais . Outros idiomas / ambientes geralmente têm funcionalidades semelhantes, basta encontrá-las.

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

  • Em suma, é muito melhor não testar contra a implementação privada.

5
Não tenho certeza se concordo com qualquer uma das três opções que você sugere. Minha preferência seria testar apenas a interface pública como você disse antes, mas garantir que, ao fazer isso, métodos privados sejam exercidos. Parte da vantagem de fazer isso é encontrar código morto, o que é improvável que ocorra se você forçar o código de teste a interromper o uso normal do idioma.
Magus

Seus métodos devem fazer uma coisa e um teste não deve considerar a implementação de forma alguma. Métodos particulares são detalhes de implementação. Se testar apenas métodos públicos significa que seus testes são testes de integração, você tem um problema de design.
Magus

Que tal tornar o método padrão / protegido e criar um teste em um projeto de teste com o mesmo pacote?
21915 RichardCypher

@RichardCypher Isso é efetivamente o mesmo que 2), pois você está alterando quais seriam as especificações do seu método para acomodar uma deficiência em seu ambiente de teste, portanto, ainda assim, é uma prática ruim.
Mark Booth

75

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

Existe alguma maneira de executar o método privado; nesse caso, você pode testá-lo dessa maneira ou não como executar o privado, nesse caso: por que diabos você está tentando testá-lo, apenas apague a maldita coisa!

No seu exemplo:

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

Por que isso seria testável? Se o método for chamado 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 particulares, é de não quebrar o encapsulamento. Você pode ter métodos privados, mas deve testá-los através 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 através dos métodos públicos que os chamam. Em particular, como você só pode escrever código para obter aprovação em um teste com falha e seus testes estão testando a API pública, todo o novo código que você escreve geralmente será público. Os métodos privados aparecem apenas como resultado de uma refatoração de método de extração , quando são retirados de um método público já existente. Mas, nesse caso, o teste original que testa o método público também cobre 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, também já são testados.


3
Testar através de métodos públicos funciona bem 99% do tempo. O desafio é o 1% do tempo em que seu método público único possui várias centenas ou milhares de linhas de código complexo por trás e todos os estados intermediários são específicos da implementação. Quando fica complexo o suficiente, tentar atingir todos os casos extremos do método público torna-se doloroso, na melhor das hipóteses. Como alternativa, testar os casos extremos quebrando o encapsulamento e expondo mais métodos como privados ou usando um kludge para que os testes chamem métodos privados resulta diretamente em casos de teste frágeis, além de serem feios.
Dan Neely

24
Métodos privados grandes e complexos são cheiros de código. Uma implementação tão complexa que não pode ser utilmente decomposta em partes componentes (com interfaces públicas) é um problema na testabilidade que revela possíveis problemas de design e arquitetura. O 1% dos casos em que o código privado é grande geralmente se beneficia do retrabalho para decompor e expor.
31512 S.Lott

13
@ Dan Neely código como esse é bastante testável, independentemente - e parte dos testes de unidade de escrita está apontando isso. Eliminar estados, dividir classes, aplicar todas as refatorações típicas e depois escrever testes de unidade. Também no TDD, como você chegou a esse ponto? Essa é uma das vantagens do TDD: escrever código testável se torna automático.
Bill K

Pelo menos internalmétodos ou métodos públicos nas internalclasses devem ser testados diretamente com bastante frequência. Felizmente, o .net suporta o teste InternalsVisibleToAttribute, mas sem ele, seria um PITA.
CodesInChaos

25

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

Por meio de testes, verificamos que o código faz o que é esperado , lança exceções apropriadas quando esperado etc. Nós realmente não nos importamos 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 faz, faz sentido evitar testar métodos privados.

Quanto ao teste de classes que não possuem métodos públicos e interagem com o mundo externo apenas por meio de eventos, você também pode testar isso 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 log 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 direta quanto aos métodos públicos.


4
+1 Uma característica importante do TDD é que ele o força a testar se os REQUISITOS são cumpridos, em vez de testar se os MÉTODOS fazem o que pensam que você faz. Portanto, a pergunta "Posso testar um método privado" é um pouco contrária ao espírito do TDD - a pergunta pode ser "Posso testar um requisito cuja implementação inclui métodos privados". E a resposta a esta pergunta é claramente sim.
Dawood ibn Kareem

6

Entendo que métodos privados não podem ser testados

Não concordo com essa afirmação, ou diria que você não testa métodos particulares diretamente . Um método público pode chamar diferentes métodos privados. Talvez o autor desejasse ter métodos "pequenos" e extraísse parte do código para um método privado com nome inteligente.

Independentemente de como o método público é escrito, seu código de teste deve cobrir todos os caminhos. Se você descobrir após os testes que uma das instruções de ramificação (if / switch) em um método privado nunca foi abordada em seus testes, você terá um problema. 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 muito a Cobertura e a NCover, para garantir que o meu teste de método público também abranja métodos privados. Sinta-se livre para escrever bons objetos OO com métodos particulares e não deixe o TDD / Testing entrar no seu caminho nesse sentido.


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 seu CUT interage. Em seguida, você pode usar uma simulação, gerar os eventos de interesse e observar se a CUT executa ou não as ações corretas em suas dependências.

Por outro lado, se você tiver um idioma com bom suporte para eventos, poderá seguir um caminho ligeiramente diferente. Não gosto quando os objetos se inscrevem nos próprios eventos; em vez disso, a fábrica que cria o objeto liga os eventos aos métodos públicos do objeto. É mais fácil testar e torna visível externamente que tipos de eventos os CUT precisam ser testados.


Essa é uma ótima idéia - "... ter a fábrica que cria o objeto conecta eventos aos métodos públicos do objeto. É mais fácil de testar e torna visível externamente que tipos de eventos o CUT precisa ser testado. "
pup

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 farão seu intestino se contorcer porque você sente que sujou seu código.

Essas são as coisas que eu 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 classe faça tende a ser exposta publicamente de qualquer maneira; portanto, pense se você realmente precisa tornar esse método inteligente privado.
  2. Minimize a quantidade de código em seus métodos particulares - você realmente deveria fazer isso de qualquer maneira - e teste indiretamente onde puder, através do comportamento de outros métodos. Você nunca espera obter 100% de cobertura de teste e talvez precise verificar alguns valores manualmente através do depurador. O uso de métodos privados para lançar exceções pode ser facilmente testado indiretamente. As propriedades particulares podem precisar ser testadas manualmente ou por outro método.
  3. Se a verificação indireta ou manual não se encaixar bem com você, adicione um evento protegido e acesse por meio de uma interface para expor algumas coisas particulares. Isso efetivamente "dobra" as regras de encapsulamento, mas evita a necessidade de enviar o código que executa seus testes. A desvantagem é que isso pode resultar em um pouco de 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 nos seus métodos para limitar como eles são usados. As chances são de que, enquanto você pensa sobre isso, pense em uma maneira melhor de implementar seus métodos, ou verá outra classe começando a tomar forma.
  5. Se você tem vários métodos particulares fazendo "coisas" para seus métodos públicos, pode haver uma nova classe aguardando para ser extraída. Você pode testar isso diretamente como uma classe separada, mas implementar como um composto em particular na classe que o usa.

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


4

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

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. Faça com que ele chame um objeto simulado e então você pode facilmente afirmar que ele faz o que você espera.

O problema é que queremos apenas testar a interação do objeto com outros objetos. Não nos importamos com o que está acontecendo dentro de um objeto. Portanto, você não deveria ter mais métodos públicos do que antes.


4

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 classe de acordo. Isso forçará você a projetar sua classe com base em como o restante do programa interage com ela e, de fato, incentivará o encapsulamento e o bom design de sua classe.


3

Em vez de uso privado modificador padrão. Em seguida, 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.


... supondo que seja Java.
Dawood ibn Kareem

ou internalem .net.
CodesInChaos

2

Alguns métodos particulares 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 nos seus métodos públicos. Um excesso de métodos privados pode ser um sinal de baixa coesão. Sua classe deve ter uma responsabilidade coesa e, muitas vezes, as pessoas tornam métodos privados para dar a aparência de coesão onde realmente não existe.

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


2

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

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 tenho um objeto Customer e quero que ele tenha um campo Id. O primeiro teste que vou escrever é um que diz algo como "customer_id_initializes_to_zero". Defino o getter para lançar uma exceção não implementada e assisto ao teste falhar. Então, a coisa mais simples que posso fazer para passar no teste é fazer com que o getter retorne zero.

A partir daí, vou para outros testes, presumivelmente aqueles que envolvem o ID do cliente como um campo funcional real. Em algum momento, provavelmente tenho que criar um campo privado que a classe do cliente use para acompanhar o que deve ser retornado pelo getter. Como exatamente eu acompanho isso? É um int simples apoio? Eu mantenho o controle de uma string e a converto em int? Eu mantenho o controle de 20 polegadas e a média delas? O mundo exterior não se importa - e seus testes TDD não se importam. Esse é um detalhe encapsulado .

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


2

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

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

Você não pode testar métodos diretamente em classes abstratas, assim como não pode testar métodos privados diretamente, 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 refatora a funcionalidade comum à medida que avança.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.