Eu li este post sobre como testar métodos privados. Geralmente não os testo, porque sempre achei mais rápido testar apenas métodos públicos que serão chamados de fora do objeto. Você testa métodos particulares? Devo sempre testá-los?
Eu li este post sobre como testar métodos privados. Geralmente não os testo, porque sempre achei mais rápido testar apenas métodos públicos que serão chamados de fora do objeto. Você testa métodos particulares? Devo sempre testá-los?
Respostas:
Eu não teste métodos unitários privados. Um método privado é um detalhe de implementação que deve estar oculto para os usuários da classe. Testar métodos privados interrompe o encapsulamento.
Se eu achar que o método privado é enorme ou complexo ou importante o suficiente para exigir seus próprios testes, basta colocá-lo em outra classe e torná-lo público lá ( Object Object ). Então, posso testar facilmente o método anteriormente privado, mas agora público, que agora vive em sua própria classe.
Qual é o objetivo dos testes?
A maioria das respostas até agora está dizendo que métodos privados são detalhes de implementação que não importam (ou pelo menos não deveriam), desde que a interface pública esteja bem testada e funcionando. Isso é absolutamente correto se seu único objetivo para testar é garantir que a interface pública funcione .
Pessoalmente, meu principal uso para testes de código é garantir que futuras alterações de código não causem problemas e ajudar meus esforços de depuração, se o fizerem. Acho que testar os métodos privados da mesma maneira que a interface pública (se não mais!) Promove esse objetivo.
Considere: Você tem o método público A, que chama o método privado B. A e B usam o método C. C é alterado (talvez por você, talvez por um fornecedor), fazendo com que A comece a falhar em seus testes. Não seria útil fazer testes para B também, mesmo que seja privado, para que você saiba se o problema está no uso de A por C, no uso de C por B ou em ambos?
Testar métodos particulares também agrega valor nos casos em que a cobertura de teste da interface pública está incompleta. Embora essa seja uma situação que geralmente queremos evitar, o teste da unidade de eficiência depende tanto dos testes de detecção de bugs quanto dos custos associados de desenvolvimento e manutenção desses testes. Em alguns casos, os benefícios da cobertura de teste de 100% podem ser considerados insuficientes para garantir os custos desses testes, produzindo lacunas na cobertura de teste da interface pública. Nesses casos, um teste bem direcionado de um método privado pode ser uma adição muito eficaz à base de código.
testDoSomething()
ou em testDoSomethingPrivate()
. Isso torna o teste menos valioso. . Aqui está mais razões para testes privados stackoverflow.com/questions/34571/... :
Costumo seguir os conselhos de Dave Thomas e Andy Hunt em seu livro Pragmatic Unit Testing :
Em geral, você não deseja quebrar nenhum encapsulamento para testar (ou, como mamãe costumava dizer, "não exponha suas partes íntimas!"). Na maioria das vezes, você deve poder testar uma classe exercitando seus métodos públicos. Se houver uma funcionalidade significativa oculta por trás de acesso privado ou protegido, isso pode ser um sinal de aviso de que há outra classe lá lutando para sair.
Mas, às vezes, não consigo parar de testar métodos privados, porque isso me dá a sensação de que estou construindo um programa completamente robusto.
Sinto-me compelido a testar funções privadas, pois estou seguindo cada vez mais uma de nossas recomendações mais recentes de controle de qualidade em nosso projeto:
Não mais que 10 em complexidade ciclomática por função.
Agora, o efeito colateral da aplicação dessa política é que muitas das minhas funções públicas muito grandes se dividem em muitas funções privadas mais focadas e melhor nomeadas .
A função pública ainda está lá (é claro), mas é essencialmente reduzida a chamada de todas essas subfunções privadas
Isso é realmente legal, porque o callstack agora é muito mais fácil de ler (em vez de um bug em uma função grande, eu tenho um bug em uma sub-subfunção com o nome das funções anteriores no callstack para me ajudar a entender 'como cheguei lá')
No entanto, agora parece mais fácil testar por unidade diretamente essas funções privadas e deixar o teste da grande função pública para algum tipo de teste de 'integração', em que um cenário precisa ser tratado.
Apenas meus 2 centavos.
Sim, eu testo funções privadas, porque, embora sejam testadas pelos seus métodos públicos, é bom no TDD (Test Driven Design) testar a menor parte do aplicativo. Mas as funções privadas não são acessíveis quando você está na classe da unidade de teste. Aqui está o que fazemos para testar nossos métodos particulares.
Por que temos métodos particulares?
Funções privadas existem principalmente em nossa classe porque queremos criar código legível em nossos métodos públicos. Não queremos que o usuário desta classe chame esses métodos diretamente, mas através de nossos métodos públicos. Além disso, não queremos alterar seu comportamento ao estender a classe (no caso de protegido), portanto, é um privado.
Quando codificamos, usamos o design controlado por teste (TDD). Isso significa que, às vezes, encontramos uma funcionalidade que é privada e deseja testar. Funções privadas não são testáveis no phpUnit, porque não podemos acessá-las na classe Test (elas são privadas).
Achamos que aqui estão três soluções:
1. Você pode testar suas partes privadas através de seus métodos públicos
Vantagens
Desvantagens
2. Se o privado é tão importante, talvez seja um código para criar uma nova classe separada para ele
Vantagens
Desvantagens
3. Altere o modificador de acesso para (final) protegido
Vantagens
Desvantagens
Exemplo
class Detective {
public function investigate() {}
private function sleepWithSuspect($suspect) {}
}
Altered version:
class Detective {
public function investigate() {}
final protected function sleepWithSuspect($suspect) {}
}
In Test class:
class Mock_Detective extends Detective {
public test_sleepWithSuspect($suspect)
{
//this is now accessible, but still not overridable!
$this->sleepWithSuspect($suspect);
}
}
Portanto, nossa unidade de teste agora pode chamar test_sleepWithSuspect para testar nossa antiga função privada.
Não gosto de testar a funcionalidade privada por alguns motivos. Eles são os seguintes (estes são os principais pontos para o pessoal do TLDR):
Vou explicar cada uma delas com um exemplo concreto. Acontece que 2) e 3) estão um pouco intricadamente conectados, então o exemplo deles é semelhante, embora eu os considere razões separadas por que você não deve testar métodos privados.
Há momentos em que o teste de métodos particulares é apropriado; é importante estar ciente das desvantagens listadas acima. Vou falar sobre isso mais detalhadamente mais tarde.
Também discuto por que o TDD não é uma desculpa válida para testar métodos privados no final.
Um dos (anti) paternos mais comuns que eu vejo é o que Michael Feathers chama de classe "Iceberg" (se você não sabe quem é Michael Feathers, compre / leia seu livro "Trabalhando Efetivamente com o Código Legado". uma pessoa que vale a pena conhecer se você é um engenheiro / desenvolvedor profissional de software). Existem outros (anti) padrões que causam esse problema, mas esse é de longe o mais comum que eu já deparei. As classes "Iceberg" têm um método público e o restante é privado (é por isso que é tentador testar os métodos privados). Isso é chamado de classe "Iceberg" porque geralmente existe um método público solitário, mas o restante da funcionalidade fica oculto sob a forma de métodos particulares.
Por exemplo, você pode querer testar GetNextToken()
chamando-a em uma sequência sucessivamente e vendo que ela retorna o resultado esperado. Uma função como essa merece um teste: esse comportamento não é trivial, especialmente se suas regras de tokenização forem complexas. Vamos fingir que não é tão complexo assim, e nós apenas queremos colocar tokens delimitados pelo espaço. Então, você escreve um teste, talvez seja algo assim (algum código psuedo independente da linguagem, espero que a idéia seja clara):
TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
re = RuleEvaluator(input_string);
ASSERT re.GetNextToken() IS "1";
ASSERT re.GetNextToken() IS "2";
ASSERT re.GetNextToken() IS "test";
ASSERT re.GetNextToken() IS "bar";
ASSERT re.HasMoreTokens() IS FALSE;
}
Bem, isso realmente parece muito bom. Queremos ter certeza de manter esse comportamento ao fazer alterações. Mas GetNextToken()
é uma função privada ! Portanto, não podemos testá-lo assim, porque ele nem compila (supondo que estamos usando alguma linguagem que realmente imponha público / privado, ao contrário de algumas linguagens de script como Python). Mas e quanto a mudar de RuleEvaluator
classe para seguir o Princípio de Responsabilidade Única (Princípio de Responsabilidade Única)? Por exemplo, parece que temos um analisador, tokenizador e avaliador presos em uma classe. Não seria melhor apenas separar essas responsabilidades? Além disso, se você criar uma Tokenizer
classe, seus métodos públicos seriam HasMoreTokens()
e GetNextTokens()
. A RuleEvaluator
turma poderia ter umTokenizer
objeto como um membro. Agora, podemos manter o mesmo teste acima, exceto que estamos testando a Tokenizer
classe em vez da RuleEvaluator
classe.
Veja como isso pode ser na UML:
Observe que esse novo design aumenta a modularidade; portanto, você pode potencialmente reutilizar essas classes em outras partes do seu sistema (antes disso, os métodos privados não são reutilizáveis por definição). Essa é a principal vantagem de desmembrar o RuleEvaluator, juntamente com um maior entendimento / localidade.
O teste seria extremamente semelhante, exceto que, na verdade, seria compilado dessa vez, já que o GetNextToken()
método agora é público na Tokenizer
classe:
TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
ASSERT tokenizer.GetNextToken() IS "1";
ASSERT tokenizer.GetNextToken() IS "2";
ASSERT tokenizer.GetNextToken() IS "test";
ASSERT tokenizer.GetNextToken() IS "bar";
ASSERT tokenizer.HasMoreTokens() IS FALSE;
}
Mesmo que você não consiga dividir seu problema em menos componentes modulares (que você pode 95% do tempo se tentar fazê-lo), basta testar as funções privadas por meio de uma interface pública. Muitas vezes, os membros privados não valem a pena testar porque serão testados através da interface pública. Muitas vezes o que vejo são testes muito parecidos, mas testam duas funções / métodos diferentes. O que acaba acontecendo é que, quando os requisitos mudam (e sempre mudam), agora você tem 2 testes quebrados em vez de 1. E se você realmente testou todos os seus métodos particulares, pode ter mais 10 testes quebrados em vez de 1. Em resumo , testando funções privadas (usandoFRIEND_TEST
ou torná-los públicos ou usando reflexão) que poderiam ser testados por meio de uma interface pública podem causar duplicação de teste . Você realmente não quer isso, porque nada machuca mais do que sua suíte de testes que o atrasa. É suposto diminuir o tempo de desenvolvimento e os custos de manutenção! Se você testar métodos particulares que são testados por meio de uma interface pública, o conjunto de testes pode muito bem fazer o oposto e aumentar ativamente os custos de manutenção e o tempo de desenvolvimento. Quando você torna pública uma função privada, ou se usa algo como FRIEND_TEST
e / ou reflexão, geralmente acaba se arrependendo a longo prazo.
Considere a seguinte implementação possível da Tokenizer
classe:
Digamos que SplitUpByDelimiter()
seja responsável por retornar uma matriz de modo que cada elemento na matriz seja um token. Além disso, digamos que GetNextToken()
é simplesmente um iterador sobre esse vetor. Portanto, seu teste público pode parecer assim:
TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
ASSERT tokenizer.GetNextToken() IS "1";
ASSERT tokenizer.GetNextToken() IS "2";
ASSERT tokenizer.GetNextToken() IS "test";
ASSERT tokenizer.GetNextToken() IS "bar";
ASSERT tokenizer.HasMoreTokens() IS false;
}
Vamos fingir que temos o que Michael Feather chama de ferramenta tateando . Esta é uma ferramenta que permite tocar em partes íntimas de outras pessoas. Um exemplo é FRIEND_TEST
de googletest, ou reflexão, se o idioma suportar.
TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
result_array = tokenizer.SplitUpByDelimiter(" ");
ASSERT result.size() IS 4;
ASSERT result[0] IS "1";
ASSERT result[1] IS "2";
ASSERT result[2] IS "test";
ASSERT result[3] IS "bar";
}
Bem, agora digamos que os requisitos mudem e a tokenização se torne muito mais complexa. Você decide que um simples delimitador de string não será suficiente e precisa de uma Delimiter
classe para lidar com o trabalho. Naturalmente, você espera que um teste seja interrompido, mas essa dor aumenta quando você testa funções privadas.
Não há "tamanho único" no software. Às vezes, é bom (e realmente ideal) "quebrar as regras". Eu defendo fortemente não testar a funcionalidade privada quando puder. Existem duas situações principais em que acho que está tudo bem:
Eu trabalhei extensivamente com sistemas legados (e é por isso que sou um grande fã de Michael Feathers), e posso dizer com segurança que às vezes é simplesmente mais seguro apenas testar a funcionalidade privada. Pode ser especialmente útil para obter "testes de caracterização" na linha de base.
Você está com pressa e precisa fazer o mais rápido possível por aqui e agora. A longo prazo, você não deseja testar métodos privados. Mas vou dizer que geralmente leva algum tempo para refatorar para resolver problemas de design. E às vezes você tem que enviar em uma semana. Tudo bem: faça o que for mais rápido e sujo e teste os métodos particulares usando uma ferramenta de busca, se é o que você acha que é a maneira mais rápida e confiável de realizar o trabalho. Mas entenda que o que você fez foi abaixo do ideal a longo prazo e considere voltar a ele (ou, se foi esquecido, mas você vê mais tarde, conserte).
Provavelmente há outras situações em que está tudo bem. Se você acha que está tudo bem e tem uma boa justificativa, faça-o. Ninguém o está impedindo. Esteja ciente dos custos potenciais.
Como um aparte, eu realmente não gosto de pessoas que usam TDD como uma desculpa para testar métodos privados. Eu pratico TDD e não acho que o TDD o force a fazer isso. Você pode escrever seu teste (para sua interface pública) primeiro e depois escrever o código para satisfazer essa interface. Às vezes, escrevo um teste para uma interface pública e o satisfazi escrevendo um ou dois métodos privados menores também (mas não testo os métodos privados diretamente, mas sei que eles funcionam ou que meu teste público falharia ) Se eu precisar testar casos extremos desse método privado, escreverei um monte de testes que os atingirão através da minha interface pública.Se você não consegue descobrir como atingir os casos extremos, esse é um sinal forte de que você precisa refatorar em pequenos componentes, cada um com seus próprios métodos públicos. É um sinal de que as funções privadas estão fazendo muito e fora do escopo da classe .
Além disso, às vezes, acho que escrevo um teste muito grande para mastigar no momento, e então penso: "eh, voltarei a esse teste mais tarde quando tiver mais API para trabalhar" (eu vou comentar e manter isso no fundo da minha mente). É aqui que muitos desenvolvedores que conheci começarão a escrever testes para suas funcionalidades particulares, usando o TDD como bode expiatório. Eles dizem "ah, bem, eu preciso de outro teste, mas para escrever esse teste, precisarei desses métodos particulares. Portanto, como não consigo escrever nenhum código de produção sem escrever um teste, preciso escrever um teste para um método privado ". Mas o que eles realmente precisam fazer é refatorar em componentes menores e reutilizáveis, em vez de adicionar / testar vários métodos particulares à sua classe atual.
Nota:
Respondi a uma pergunta semelhante sobre o teste de métodos particulares usando o GoogleTest há pouco tempo. Modifiquei principalmente essa resposta para ser mais independente de idioma aqui.
PS Aqui está a palestra relevante sobre aulas de iceberg e ferramentas de tatear de Michael Feathers: https://www.youtube.com/watch?v=4cVZvoFGJTU
_
, ele indicará "ei, isso é 'privado'. Você pode usá-lo, mas a divulgação completa, ele não foi projetado para reutilização e deve ser usado apenas se você realmente saber o que você está fazendo ". Você pode adotar a mesma abordagem em qualquer idioma: torne esses membros públicos, mas marque-os com uma liderança _
. Ou talvez essas funções realmente devam ser privadas e testadas por meio de uma interface pública (consulte a resposta para obter mais detalhes). É de caso a caso, nenhuma regra geral
Eu acho que é melhor apenas testar a interface pública de um objeto. Do ponto de vista do mundo exterior, apenas o comportamento da interface pública é importante e é para isso que seus testes de unidade devem ser direcionados.
Depois de escrever alguns testes de unidade sólidos para um objeto, você não precisa voltar e alterar esses testes apenas porque a implementação por trás da interface mudou. Nessa situação, você arruinou a consistência do seu teste de unidade.
Se seu método privado não for testado chamando seus métodos públicos, o que está fazendo? Eu estou falando privado não protegido ou amigo.
Se o método privado estiver bem definido (ou seja, tiver uma função que pode ser testada e não deve mudar ao longo do tempo), então sim. Testo tudo o que é testável, onde faz sentido.
Por exemplo, uma biblioteca de criptografia pode ocultar o fato de executar criptografia de bloco com um método privado que criptografa apenas 8 bytes por vez. Eu escreveria um teste de unidade para isso - ele não deve mudar, mesmo estando oculto, e se quebrar (devido a aprimoramentos futuros de desempenho, por exemplo), quero saber que é a função privada que quebrou, não apenas que uma das funções públicas quebrou.
Acelera a depuração mais tarde.
-Adão
Se você estiver desenvolvendo test-driven (TDD), testará seus métodos particulares.
Não sou especialista neste campo, mas o teste de unidade deve testar o comportamento, não a implementação. Os métodos privados são estritamente parte da implementação, portanto o IMHO não deve ser testado.
Testamos métodos privados por inferência, ou seja, procuramos uma cobertura total de testes de classe de pelo menos 95%, mas apenas nossos testes são chamados a métodos públicos ou internos. Para obter a cobertura, precisamos fazer várias chamadas para o público / interno com base nos diferentes cenários que podem ocorrer. Isso torna nossos testes mais atentos ao objetivo do código que eles estão testando.
A resposta de Trumpi à postagem que você vinculou é a melhor.
Eu estive discutindo sobre esse assunto por um tempo, especialmente tentando minha mão no TDD.
Eu me deparei com dois posts que acho que abordam esse problema completamente o suficiente no caso do TDD.
Em suma:
Ao usar técnicas de desenvolvimento orientadas a teste (design), métodos privados devem surgir apenas durante o processo de refatoração de código já em funcionamento e testado.
Pela própria natureza do processo, qualquer funcionalidade simples de implementação extraída de uma função completamente testada será auto-testada (ou seja, cobertura de teste indireta).
Para mim, parece claro o suficiente que, na parte inicial da codificação, a maioria dos métodos terá funções de nível superior porque elas estão encapsulando / descrevendo o design.
Portanto, esses métodos serão públicos e testá-los será bastante fácil.
Os métodos privados virão mais tarde, quando tudo estiver funcionando bem e estamos considerando a questão da legibilidade e limpeza .
Como citado acima, "Se você não testar seus métodos particulares, como você sabe que eles não quebrarão?"
Esta é uma questão importante. Um dos grandes pontos dos testes de unidade é saber onde, quando e como algo quebrou o mais rápido possível. Diminuindo assim uma quantidade significativa de esforço de desenvolvimento e controle de qualidade. Se tudo o que for testado for público, você não terá cobertura e delineamento honestos dos internos da turma.
Eu descobri que uma das melhores maneiras de fazer isso é simplesmente adicionar a referência de teste ao projeto e colocar os testes em uma classe paralela aos métodos privados. Coloque a lógica de construção apropriada para que os testes não sejam incorporados ao projeto final.
Então você tem todos os benefícios de ter esses métodos testados e pode encontrar problemas em segundos, em vez de minutos ou horas.
Então, em resumo, sim, teste de unidade seus métodos particulares.
Você não deveria . Se seus métodos particulares tiverem complexidade suficiente que deve ser testada, você deverá colocá-los em outra classe. Mantenha alta coesão , uma classe deve ter apenas um propósito. A interface pública da classe deve ser suficiente.
Se você não testar seus métodos particulares, como você sabe que eles não vão quebrar?
Obviamente, depende da linguagem. No passado, com c ++, eu declarei a classe testing como uma classe friend. Infelizmente, isso exige que seu código de produção conheça a classe de teste.
Entendo o ponto de vista em que métodos privados são considerados como detalhes de implementações e não precisam ser testados. E eu continuaria com essa regra se tivéssemos que desenvolver apenas fora do objeto. Mas nós, somos algum tipo de desenvolvedor restrito que está desenvolvendo apenas fora dos objetos, chamando apenas seus métodos públicos? Ou na verdade também estamos desenvolvendo esse objeto? Como não estamos programados para objetos externos, provavelmente precisaremos chamar esses métodos privados para novos métodos públicos que estamos desenvolvendo. Não seria ótimo saber que o método privado resiste a todas as probabilidades?
Eu sei que algumas pessoas podem responder que, se estamos desenvolvendo outro método público para esse objeto, este deve ser testado e é isso (o método privado pode continuar vivendo sem teste). Mas isso também é verdade para qualquer método público de um objeto: ao desenvolver um aplicativo Web, todos os métodos públicos de um objeto são chamados de métodos de controladores e, portanto, podem ser considerados como detalhes de implementação de controladores.
Então, por que estamos testando objetos de unidade? Porque é realmente difícil, para não dizer impossível, ter certeza de que estamos testando os métodos dos controladores com a entrada apropriada que acionará todas as ramificações do código subjacente. Em outras palavras, quanto mais alto estivermos na pilha, mais difícil será testar todo o comportamento. E assim é o mesmo para métodos privados.
Para mim, a fronteira entre métodos privados e públicos é um critério psicológico quando se trata de testes. Os critérios mais importantes para mim são:
Se eu achar que o método privado é enorme ou complexo ou importante o suficiente para exigir seus próprios testes, basta colocá-lo em outra classe e torná-lo público lá (Método Objeto). Então eu posso testar facilmente o método anteriormente privado, mas agora público, que agora vive em sua própria classe.
Eu nunca entendo o conceito de teste de unidade, mas agora sei qual é o objetivo.
Um teste de unidade não é um teste completo . Portanto, não substitui o controle de qualidade e o teste manual. O conceito de TDD nesse aspecto está errado, pois você não pode testar tudo, incluindo métodos privados, mas também métodos que usam recursos (especialmente recursos que não temos controle). TDD está baseando toda a sua qualidade é algo que não poderia ser alcançado.
Um teste de unidade é mais um teste de pivô. Você marca algum pivô arbitrário e o resultado do pivô deve permanecer o mesmo.
Público x privado não é uma distinção útil para quais APIs devem chamar de seus testes, nem método vs. classe. A maioria das unidades testáveis é visível em um contexto, mas oculta em outros.
O que importa é a cobertura e os custos. Você precisa minimizar os custos enquanto atinge as metas de cobertura do seu projeto (linha, ramificação, caminho, bloco, método, classe, classe de equivalência, caso de uso ... o que a equipe decidir).
Portanto, use ferramentas para garantir a cobertura e projete seus testes para causar menos custos (curto e longo prazo ).
Não faça testes mais caros do que o necessário. Se for mais barato testar apenas pontos de entrada públicos, faça isso. Se for mais barato testar métodos privados, faça isso.
À medida que for adquirindo experiência, você se tornará melhor em prever quando vale a pena refatorar para evitar custos de manutenção de teste a longo prazo.
Se o método for suficientemente significativo / complexo o suficiente, geralmente o tornarei "protegido" e o testarei. Alguns métodos serão deixados privados e testados implicitamente como parte de testes de unidade para os métodos público / protegido.
Vejo que muitas pessoas estão na mesma linha de pensamento: teste no nível público. mas não é isso que nossa equipe de controle de qualidade faz? Eles testam a entrada e a saída esperada. Se como desenvolvedores testamos apenas os métodos públicos, estamos simplesmente refazendo o trabalho do controle de qualidade e não agregando valor ao "teste de unidade".
A resposta para "Devo testar métodos particulares?" As vezes". Normalmente você deve testar na interface de suas classes.
Aqui está um exemplo:
class Thing
def some_string
one + two
end
private
def one
'aaaa'
end
def two
'bbbb'
end
end
class RefactoredThing
def some_string
one + one_a + two + two_b
end
private
def one
'aa'
end
def one_a
'aa'
end
def two
'bb'
end
def two_b
'bb'
end
end
Em RefactoredThing
que você tem agora 5 testes, 2 dos quais você teve que atualizar para refatoração, mas a funcionalidade do seu objeto realmente não mudou. Então, digamos que as coisas são mais complexas que isso e você tem algum método que define a ordem da saída, como:
def some_string_positioner
if some case
elsif other case
elsif other case
elsif other case
else one more case
end
end
Isso não deve ser executado por um usuário externo, mas sua classe de encapsulamento pode ser muito pesada para executar toda essa lógica repetidamente. Nesse caso, talvez você prefira extrair isso para uma classe separada, fornecer uma interface a essa classe e testar com ela.
E, finalmente, digamos que seu objeto principal seja super pesado, e o método seja bastante pequeno e você realmente precisa garantir que a saída esteja correta. Você está pensando: "Eu tenho que testar esse método particular!". Você talvez possa tornar seu objeto mais leve, passando alguns dos trabalhos pesados como um parâmetro de inicialização? Então você pode passar algo mais leve e testar contra isso.
Um ponto principal é
Se testamos para garantir a correção da lógica, e um método privado está carregando uma lógica, devemos testá-la. Não é? Então, por que vamos pular isso?
Escrever testes com base na visibilidade dos métodos é uma idéia completamente irrelevante.
Inversamente
Por outro lado, chamar um método privado fora da classe original é um problema principal. E também há limitações para zombar de um método privado em algumas ferramentas de zombaria. (Ex: Mockito )
Embora existam algumas ferramentas como o Power Mock que suportam isso, é uma operação perigosa. O motivo é que ele precisa invadir a JVM para conseguir isso.
Uma solução alternativa que pode ser feita é (se você deseja gravar casos de teste para métodos particulares)
Declare esses métodos particulares como protegidos . Mas pode não ser conveniente para várias situações.
Não se trata apenas de métodos ou funções públicas ou privadas, mas de detalhes de implementação. Funções privadas são apenas um aspecto dos detalhes da implementação.
Afinal, o teste de unidade é uma abordagem de teste de caixa branca. Por exemplo, quem usa a análise de cobertura para identificar partes do código que foram negligenciadas nos testes até agora, entra nos detalhes da implementação.
A) Sim, você deve testar os detalhes da implementação:
Pense em uma função de classificação que, por razões de desempenho, use uma implementação privada do BubbleSort se houver até 10 elementos e uma implementação privada de uma abordagem de classificação diferente (por exemplo, heapsort) se houver mais de 10 elementos. A API pública é a de uma função de classificação. Sua suíte de testes, no entanto, utiliza melhor o conhecimento de que realmente existem dois algoritmos de classificação usados.
Neste exemplo, certamente, você pode executar os testes na API pública. No entanto, isso exigiria um número de casos de teste que executam a função de classificação com mais de 10 elementos, de modo que o algoritmo heapsort seja suficientemente bem testado. A existência desses casos de teste por si só é uma indicação de que o conjunto de testes está conectado aos detalhes de implementação da função.
Se os detalhes da implementação da função de classificação mudarem, talvez da maneira que o limite entre os dois algoritmos de classificação seja alterado ou que o heapsort seja substituído por mergesort ou o que for: Os testes existentes continuarão funcionando. Seu valor, no entanto, é questionável e eles provavelmente precisam ser reformulados para testar melhor a função de classificação alterada. Em outras palavras, haverá um esforço de manutenção, apesar do fato de os testes terem sido realizados na API pública.
B) Como testar detalhes de implementação
Uma razão pela qual muitas pessoas argumentam que não se deve testar funções privadas ou detalhes de implementação é que os detalhes de implementação têm mais probabilidade de mudar. Essa maior probabilidade de mudança, pelo menos, é um dos motivos para ocultar os detalhes da implementação por trás das interfaces.
Agora, suponha que a implementação por trás da interface contenha partes particulares maiores para as quais testes individuais na interface interna possam ser uma opção. Algumas pessoas argumentam que essas peças não devem ser testadas quando privadas, devem ser transformadas em algo público. Uma vez público, o teste de unidade desse código seria bom.
Isso é interessante: embora a interface fosse interna, era provável que ela mudasse, sendo um detalhe de implementação. Adotar a mesma interface, torná-la pública faz alguma transformação mágica, ou seja, transformá-la em uma interface com menor probabilidade de mudança. Obviamente, há alguma falha nessa argumentação.
No entanto, há alguma verdade por trás disso: ao testar os detalhes da implementação, em particular o uso de interfaces internas, deve-se esforçar-se por usar interfaces que provavelmente permanecerão estáveis. A probabilidade de alguma interface ser estável não é, no entanto, simplesmente decidível com base em se é pública ou privada. Nos projetos do mundo em que trabalho há algum tempo, as interfaces públicas também costumam mudar bastante, e muitas interfaces privadas permanecem intocadas há séculos.
Ainda assim, é uma boa regra geral usar a "porta da frente primeiro" (consulte http://xunitpatterns.com/Principles%20of%20Test%20Automation.html ). Mas lembre-se de que é chamada "porta da frente primeiro" e não "porta da frente apenas".
C) Resumo
Teste também os detalhes da implementação. Prefira testar em interfaces estáveis (públicas ou privadas). Se os detalhes da implementação mudarem, também é necessário revisar os testes na API pública. Transformar algo privado em público não altera magicamente sua estabilidade.
Sim, você deve testar métodos particulares, sempre que possível. Por quê? Para evitar uma explosão desnecessária no espaço de estados de casos de teste que acabam testando implicitamente as mesmas funções privadas repetidamente nas mesmas entradas. Vamos explicar o porquê com um exemplo.
Considere o seguinte exemplo um pouco artificial. Suponha que desejamos expor publicamente uma função que use 3 números inteiros e retorne true se e somente se esses 3 números inteiros forem primos. Podemos implementá-lo assim:
public bool allPrime(int a, int b, int c)
{
return andAll(isPrime(a), isPrime(b), isPrime(c))
}
private bool andAll(bool... boolArray)
{
foreach (bool b in boolArray)
{
if(b == false) return false;
}
return true;
}
private bool isPrime(int x){
//Implementation to go here. Sorry if you were expecting a prime sieve.
}
Agora, se adotássemos a abordagem estrita de que apenas funções públicas deveriam ser testadas, poderíamos apenas testar allPrime
e não isPrime
ou andAll
.
Como testador, que poderia estar interessado em cinco possibilidades para cada argumento: < 0
, = 0
, = 1
, prime > 1
, not prime > 1
. Mas, para ser completo, teríamos que ver também como todas as combinações de argumentos se encaixam. Portanto, são 5*5*5
125 casos de teste que precisaríamos testar completamente essa função, de acordo com nossas intuições.
Por outro lado, se pudéssemos testar as funções privadas, poderíamos cobrir tanto terreno com menos casos de teste. Precisávamos de apenas 5 casos de teste isPrime
para testar no mesmo nível da nossa intuição anterior. E pela hipótese do pequeno escopo proposta por Daniel Jackson, precisaríamos apenas testar a andAll
função até um pequeno comprimento, por exemplo, 3 ou 4. O que seria no máximo 16 testes a mais. Então 21 testes no total. Em vez de 125. É claro que provavelmente quereríamos executar alguns testes allPrime
, mas não nos sentiríamos tão obrigados a cobrir exaustivamente todas as 125 combinações de cenários de entrada com os quais dissemos que nos importávamos. Apenas alguns caminhos felizes.
Um exemplo artificial, com certeza, mas era necessário para uma demonstração clara. E o padrão se estende ao software real. As funções privadas geralmente são os blocos de construção de nível mais baixo e, portanto, são frequentemente combinadas para gerar uma lógica de nível mais alto. Ou seja, em níveis mais altos, temos mais repetições das coisas de nível inferior devido às várias combinações.
isPrime
são verdadeiramente independentes; portanto, testar todas as combinações às cegas não tem propósito. Em segundo lugar, marcar uma função pura chamada isPrime
privada viola tantas regras de design que eu nem sei por onde começar. isPrime
muito claramente deve ser uma função pública. Dito isto, eu entendo o que você está dizendo, independentemente deste exemplo extremamente pobre. No entanto, foi construído com base na premissa de que você deseja fazer testes combinados, quando em sistemas de software reais isso raramente é uma boa idéia.