Como evitar a necessidade de testar métodos particulares por unidade

15

Eu sei que você não deveria testar métodos particulares e, se parecer que você precisa, pode haver uma classe lá esperando para sair.

Mas não quero ter um zilhão de classes apenas para poder testar suas interfaces públicas e acho que, para muitas classes, se apenas testar os métodos públicos, acabo tendo que zombar de muitas dependências e os testes de unidade são enorme e difícil de seguir.

Prefiro zombar dos métodos privados ao testar os públicos e zombar de dependências externas ao testar os privados.

Eu sou louco?

Fran Sevillano
fonte
Um teste de unidade completo deve abranger implicitamente todos os membros particulares de uma determinada classe, porque, embora você não possa chamá-los diretamente, o comportamento deles ainda afetará a saída. Se não o fazem, por que estão lá em primeiro lugar? Lembre-se, nos testes de unidade, o que importa é o resultado, não como o resultado foi alcançado.
GordonM

Respostas:

23

Você está parcialmente certo - não deve testar diretamente métodos privados. Os métodos privados em uma classe devem ser invocados por um ou mais métodos públicos (talvez indiretamente - um método privado chamado por um método público pode invocar outros métodos privados). Portanto, ao testar seus métodos públicos, você também testará seus métodos privados. Se você tiver métodos particulares que não foram testados, seus casos de teste são insuficientes ou os métodos privados não são utilizados e podem ser removidos.

Se você estiver adotando uma abordagem de teste de caixa branca, considere os detalhes de implementação de seus métodos privados ao construir testes de unidade em torno de seus métodos públicos. Se você está adotando uma abordagem de caixa preta, não deve testar os detalhes de implementação nos métodos público ou privado, mas sim o comportamento esperado.

Pessoalmente, prefiro uma abordagem de caixa branca para testes de unidade. Posso criar testes para colocar os métodos e classes em teste em diferentes estados que causam um comportamento interessante em meus métodos públicos e privados e depois afirmar que os resultados são o que eu espero.

Portanto - não zombe de seus métodos particulares. Use-os para entender o que você precisa testar, a fim de fornecer uma boa cobertura da funcionalidade que você fornece. Isto é especialmente verdade no nível do teste de unidade.

Thomas Owens
fonte
1
"Não zombe seus métodos privados" Sim, eu posso entender teste, quando você está zombando deles você pode ter ultrapassado a linha fina fina de louco
Ewan
Sim, mas como eu disse, meu problema com o teste de caixa branca é que geralmente zombar de todas as dependências de métodos públicos e privados resulta em métodos de teste realmente grandes. Alguma idéia de como lidar com isso?
Fran Sevillano
8
@FranSevillano Se você precisar fazer um stub ou zombar tanto, eu examinaria o seu design geral. Algo parece errado.
Thomas Owens
Uma ferramenta de cobertura de código ajuda nisso.
217 Andy
5
@FranSevillano: Não há muitas boas razões para uma classe que faz uma coisa para ter toneladas de dependências. Se você tem muitas dependências, provavelmente tem uma classe de Deus.
Mooing Duck
4

Eu acho que é uma péssima idéia.

O problema com a criação de testes de unidade de membros privados é que ele se encaixa mal no ciclo de vida do produto.

A razão pela qual você escolheu tornar esses métodos privados é que eles não são essenciais para o que suas classes estão tentando fazer - apenas auxiliares na maneira como você implementa essa funcionalidade no momento. Ao refatorar, esses detalhes particulares provavelmente são candidatos a alterações e causarão atrito com a refatoração.

Além disso, a principal diferença entre membros públicos e privados é que você deve estar pensando cuidadosamente em sua API pública, documentando-a bem e verificando-a bem (asserções, etc.). Mas com uma API privada, seria inútil pensar com cuidado (um esforço desperdiçado, pois seu uso é tão localizado). Colocar métodos privados em testes de unidade significa criar dependências externas nesses métodos. Significando que a API precisa ser estável e bem documentada (já que alguém precisa descobrir por que esses testes de unidade falharam se / quando o fazem).

Eu sugiro:

  • REPENSAR se esses métodos devem ser privados
  • Escreva testes únicos (apenas para garantir que estão corretos agora, torne-os públicos temporariamente para que você possa testar e exclua o teste)
  • Teste condicional incorporado em sua implementação usando #ifdef e asserções (os testes são feitos apenas em compilações de depuração).

Aprecie sua inclinação para testar essa funcionalidade e que é difícil testá-la por meio de sua API pública atual. Mas eu pessoalmente valorizo ​​mais a modularidade do que a cobertura de teste.

Lewis Pringle
fonte
6
Discordo. IMO, isso é 100% correto para testes de integração. Mas com testes de unidade as coisas são diferentes; O objetivo do teste de unidade é identificar onde está um erro, estreito o suficiente para que você possa corrigi-lo rapidamente. Eu me encontro frequentemente nessa situação: tenho muito poucos métodos públicos, porque esse é realmente o valor principal das minhas classes (como você disse que deveria fazer). No entanto, também evito escrever 400 métodos de linha, nem públicos nem privados. Portanto, meus poucos métodos públicos só podem alcançar seu objetivo com a ajuda de dezenas de métodos privados. Isso é muito código para "corrigi-lo rapidamente". Eu tenho que iniciar o depurador etc.
marstato
6
@ marstato: sugestão: comece a escrever os testes primeiro e mude de idéia sobre os unittests: eles não encontram bugs, mas verifique se o código funciona como o desenvolvedor pretendia.
Timothy Truckle
@marstato Obrigado! Sim. Os testes sempre passam na primeira vez, quando você faz o check-in (ou você não o teria feito!). Eles são úteis, à medida que você desenvolve o código, avisando quando você quebra alguma coisa e, se você tiver BOM testes de regressão, eles fornecerão CONFORTO / ORIENTAÇÃO sobre onde procurar problemas (não nas coisas estáveis e regressão testada).
Lewis Pringle
@marstato "O objetivo do teste de unidade é identificar onde está um erro" - Esse é exatamente o mal-entendido que leva à pergunta do OP. O objetivo do teste de unidade é verificar o comportamento pretendido (e de preferência documentado) de uma API.
user560822
4
@marstato O nome "teste de integração" vem do teste de que vários componentes funcionam juntos (ou seja, eles se integram corretamente). O teste de unidade está testando que um único componente faz o que deve fazer isoladamente, o que basicamente significa que sua API pública funciona conforme documentado / necessário. Nenhum desses termos diz nada sobre se você inclui testes da lógica de implementação interna como parte de garantir que a integração funcione ou que a unidade única funcione.
Ben
3

Os UnitTests testam o comportamento observável do público , não o código, onde "público" significa: retornar valores e comunicação com dependências.

Uma "unidade" é qualquer código que resolve o mesmo problema (ou mais precisamente: tem o mesmo motivo para mudar). Pode ser um método único ou várias classes.

A principal razão pela qual você não deseja testar private methodsé: eles são detalhes de implementação e você pode alterá-los durante a refatoração (melhore seu código aplicando os princípios de OO sem alterar a funcionalidade). É exatamente quando você não deseja que seus unittests mudem, para que eles possam garantir que o comportamento do seu CuT não mudou durante a refatoração.

Mas não quero ter um zilhão de classes apenas para poder testar suas interfaces públicas e acho que, para muitas classes, se apenas testar os métodos públicos, acabo tendo que zombar de muitas dependências e os testes de unidade são enorme e difícil de seguir.

Normalmente, experimento o oposto: quanto menores as classes (menos responsabilidade elas têm), menos dependências elas têm e mais fáceis são os unittests, tanto para escrever quanto para ler.

Idealmente, você aplica o mesmo nível de padrão de abstração às suas aulas. Isso significa que suas classes fornecem alguma lógica de negócios (de preferência como "funções puras" trabalhando em seus parâmetros apenas sem manter um estado próprio) (x) ou chamam métodos em outros objetos, não os dois ao mesmo tempo.

Dessa forma, desestabilizar o comportamento dos negócios é um pedaço de bolo e os objetos de "delegação" geralmente são simples demais para falhar (sem ramificação, sem alteração de estado), de modo que não é necessário desestatizar e seus testes podem ser deixados para testes de integração ou módulo

Timothy Truckle
fonte
1

O teste de unidade tem algum valor quando você é o único programador trabalhando no código, especialmente se o aplicativo é muito grande ou muito complexo. Onde o teste de unidade se torna essencial é quando você tem um número maior de programadores trabalhando na mesma base de código. O conceito de teste de unidade foi introduzido para abordar algumas das dificuldades de trabalhar nessas equipes maiores.

A razão pela qual os testes de unidade ajudam equipes maiores é sobre contratos. Se meu código está fazendo chamadas para código escritas por outra pessoa, estou fazendo suposições sobre o que o código da outra pessoa fará em várias situações. Desde que essas suposições ainda sejam verdadeiras, meu código ainda funcionará, mas como sei quais suposições são válidas e como sei quando essas suposições foram alteradas?

É aqui que entram os testes de unidade. O autor de uma classe cria testes de unidade para documentar o comportamento esperado de sua classe. O teste de unidade define todas as maneiras válidas de usar a classe e a execução do teste de unidade valida se esses casos de uso funcionam conforme o esperado. Outro programador que deseja fazer uso de sua classe pode ler seus testes de unidade para entender o comportamento que eles podem esperar de sua classe e usar isso como base para suas suposições sobre como sua classe funciona.

Dessa maneira, as assinaturas de método público da classe e os testes de unidade juntos formam um contrato entre o autor da classe e os outros programadores que fazem uso dessa classe em seu código.

Nesse cenário, o que acontece se você incluir o teste de métodos privados? Claramente, não faz sentido.

Se você é o único programador trabalhando no seu código e deseja usar o teste de unidade como uma maneira de depurar seu código, não vejo nenhum mal nisso, é apenas uma ferramenta e você pode usá-lo de qualquer maneira que funcione para você, mas não foi a razão pela qual o teste de unidade foi introduzido e não fornece os principais benefícios do teste de unidade.

bikeman868
fonte
Eu luto com isso, porque zombo das dependências, de modo que, se alguma dependência mudar de comportamento, meu teste não falhará. Essa falha deveria ocorrer em um teste de integração e não no teste de unidade?
Todd
O mock deve se comportar de forma idêntica à implementação real, mas não possui dependências. Se a implementação real mudar para que agora sejam diferentes, isso aparecerá como uma falha no teste de integração. Pessoalmente, considero o desenvolvimento de zombarias e a implementação real uma tarefa. Eu não construo zombarias como parte dos testes de unidade de escrita. Dessa maneira, quando altero o comportamento de minhas classes e altero a simulação para corresponder, a execução dos testes de unidade identificará todas as outras classes que foram quebradas por essa alteração.
bikeman868
1

Antes de responder a essa pergunta, você precisa decidir o que realmente deseja alcançar.

Você escreve código. Você espera que ele cumpra seu contrato (em outras palavras, ele faz o que deve fazer. Escrever o que deve fazer é um passo gigantesco para algumas pessoas).

Para estar razoavelmente convencido de que o código faz o que deveria, você o observa por tempo suficiente ou escreve um código de teste que testa casos suficientes para convencê-lo "se o código passar em todos esses testes, está correto".

Frequentemente, você está interessado apenas na interface definida publicamente de algum código. Se eu usar sua biblioteca, não me importo como você a fez funcionar corretamente, apenas se ela funciona corretamente. Verifico se sua biblioteca está correta executando testes de unidade.

Mas você está criando a biblioteca. Conseguir que funcione corretamente pode ser difícil de alcançar. Digamos que eu só me importo com a biblioteca executando a operação X corretamente, então eu tenho um teste de unidade para X. Você, o desenvolvedor responsável por criar a biblioteca, implementa o X combinando as etapas A, B e C, que são totalmente não triviais. Para que sua biblioteca funcione, adicione testes para verificar se A, B e C funcionam corretamente. Você quer esses testes. Dizer "você não deve ter testes de unidade para métodos privados" é inútil. Você deseja testes para esses métodos particulares. Talvez alguém lhe diga que o teste de unidade de métodos privados está errado. Mas isso significa apenas que você não pode chamá-los de "testes de unidade", mas de "testes particulares" ou o que você quiser.

A linguagem Swift resolve o problema que você não deseja expor A, B, C como métodos públicos, apenas porque deseja testá-lo, atribuindo às funções um atributo "testável". O compilador permite que métodos testáveis ​​privados sejam chamados de testes de unidade, mas não de código que não seja de teste.

gnasher729
fonte
0

Sim, você é louco .... COMO UMA RAPOSA!

Existem algumas maneiras de testar métodos particulares, alguns dos quais dependem da linguagem.

  • reflexão! regras são feitas para serem quebradas!
  • protegê-los, herdar e substituir
  • friend / InternalsVisibleTo classes

No geral, se você deseja testar métodos privados, provavelmente deseja movê-los para métodos públicos em uma dependência e testar / injetar isso.

Ewan
fonte
Eu não sei, nos meus olhos, você explicou que não é uma boa idéia, mas continuou a responder à pergunta
Liath
Eu pensei que tinha todas as bases cobertas :(
Ewan
3
Votei positivamente, porque a ideia de expor seus métodos auxiliares privados à API pública apenas para testá-los é loucura, e sempre pensei nisso.
Robert Harvey
0

Mantenha-se pragmático. Testar casos especiais em métodos privados por meio da configuração do estado da instância e dos parâmetros para um método público para que esses casos ocorram lá, geralmente é muito complicado.

Eu adiciono um internalacessador extra (junto com a bandeira InternalsVisibleTodo assembly de teste), claramente nomeado DoSomethingForTesting(parameters), para testar esses métodos "particulares".

Obviamente, a implementação pode mudar de alguma forma e esses testes, incluindo os acessadores, tornam-se obsoletos. Isso ainda é melhor do que casos não testados ou testes ilegíveis.

Bernhard Hiller
fonte