Como posso defender o teste de unidade em código privado?

15

Estou tentando defender o teste de unidade no meu grupo de trabalho, mas uma objeção que recebo é que ele deve ser usado apenas para API exportada externamente (que é apenas uma parte mínima e não crítica do nosso sistema), e não para uso interno e privado código (que agora possui apenas testes funcionais).

Embora eu ache que esse teste de unidade possa e deva ser aplicado a todo o código, como posso convencer meus colegas de trabalho?

Wizard79
fonte
3
Se você tem métodos particulares que sente necessidade de testar, isso geralmente é um sinal de que seu código está violando o SRP e há outra classe lá clamando para ser extraída e testada por si mesma.
Paddyslacker
@ Paddyslacker: Eu sinto que todo o código precisa ser testado. Eu não vejo por que uma unidade de código que segue o princípio da responsabilidade única não devem ser submetidos a testes de unidade ...
Wizard79
4
@lorenzo, você perdeu o meu argumento; talvez eu não tenha feito isso muito bem. Se você extrair esses métodos particulares para outra classe, eles precisarão ser acessíveis a partir da sua classe original. Como os métodos agora são públicos, eles precisam ser testados. Eu não estava sugerindo que eles não deveriam ser testados, estava implicando que, se você sentir a necessidade de testar diretamente os métodos, é provável que eles não sejam privados.
Paddyslacker
@ Paddyslacker: sinto a necessidade de testar diretamente também métodos privados. Por que você acha que eles não deveriam ser privados?
Wizard79
6
Ao testar métodos particulares, você está quebrando a abstração. Você deve testar o estado e / ou o comportamento, e não a implementação, em testes de unidade. Seus exemplos / cenários devem poder verificar qual é o resultado do código privado - se você achar isso difícil, então, como diz Paddyslacker, isso pode significar que você está violando o SRP. Também pode significar que você não destilou seus exemplos para ser verdadeiramente representativo do que seu código está fazendo.
FinnNk

Respostas:

9

Seus colegas de trabalho podem estar confundindo testes de unidade verdadeiros com testes de integração. Se o seu produto é (ou possui) uma API, os testes de integração podem ser programados como casos de teste do NUnit. Algumas pessoas acreditam erroneamente que esses são testes de unidade.

Você pode tentar convencer seus colegas de trabalho com o seguinte (tenho certeza de que você já conhece essas coisas, tudo o que estou dizendo é que apontar para seus colegas de trabalho pode ajudar):

  • Cobertura de teste . Meça a porcentagem real de cobertura de teste desses testes de integração. Esta é uma verificação da realidade para aqueles que nunca executaram a cobertura do teste. Como é difícil exercer todos os caminhos lógicos quando a entrada está a várias camadas de distância, a cobertura do teste chega a algo entre 20% e 50%. Para obter mais cobertura, seus colegas de trabalho precisam gravar testes de unidade reais e isolados.
  • Configuração . Implante o mesmo software em teste e talvez você possa demonstrar aos colegas como é difícil executar os testes em um ambiente diferente. Caminhos para vários arquivos, cadeias de conexão de banco de dados, URLs de serviços remotos etc. - tudo isso se soma.
  • Tempo de execução . A menos que os testes sejam verdadeiros testes de unidade e possam ser executados na memória, eles levarão muito tempo para serem executados.
azheglov
fonte
12

Os motivos para usar testes de unidade em código interno / privado são exatamente os mesmos que para APIs suportadas externamente:

  • Eles impedem a ocorrência de erros (os testes de unidade fazem parte do seu conjunto de testes de regressão).
  • Eles documentam (em um formato executável!) Que o código funciona.
  • Eles fornecem uma definição executável do que "o código funciona" significa.
  • Eles fornecem um meio automatizado de demonstrar que o código realmente corresponde às especificações (conforme definido no ponto acima).
  • Eles mostram como a unidade / classe / módulo / função / método falha na presença de entrada inesperada.
  • Eles fornecem exemplos de como usar a unidade, o que é uma ótima documentação para os novos membros da equipe.
Frank Shearar
fonte
8

Se você quer dizer privado da maneira que eu acho, então não - você não deveria estar testando a unidade. Você deve apenas testar / observar o comportamento / estado da unidade. Você pode estar perdendo o ponto por trás do ciclo "vermelho-verde-refator" do TDD (e se você não estiver testando primeiro, aplica-se o mesmo princípio). Depois que os testes são escritos e passam, você não deseja que eles sejam alterados durante a refatoração. Se você for forçado a testar a unidade de funcionalidade privada, provavelmente isso significa que os testes de unidade em torno da funcionalidade pública são falhos. Se for difícil e complexo escrever testes em torno do código público, talvez sua classe esteja fazendo muito ou seu problema não esteja claramente definido.

Pior ainda, com o tempo, seus testes de unidade se tornarão uma bola e uma corrente que o desacelerarão sem agregar nenhum valor (alterar a implementação, por exemplo, otimização ou remoção de duplicação, não deve afetar os testes de unidade). O código interno deve, no entanto, ser testado em unidade, pois o comportamento / estado é observável (apenas de maneira restrita).

Quando fiz o teste de unidade, fiz todos os tipos de truques para testar coisas particulares, mas agora, com alguns anos de experiência, vejo isso pior do que uma perda de tempo.

Aqui está um exemplo bobo, é claro que na vida real você teria mais testes do que estes:

Digamos que você tenha uma classe que retorna uma lista classificada de strings - você deve verificar se o resultado está classificado, não como ele realmente classifica essa lista. Você pode iniciar sua implementação com um único algoritmo que apenas classifica a lista. Feito isso, seu teste não precisará ser alterado se você alterar seu algoritmo de classificação. Neste ponto, você tem um único teste (supondo que a classificação esteja incorporada na sua classe):

  1. Meu resultado está classificado?

Agora, digamos que você queira dois algoritmos (talvez um seja mais eficiente em algumas circunstâncias, mas não em outras), então cada algoritmo pode (e geralmente deve) ser fornecido por uma classe diferente e sua classe os escolhe - você pode verificar se isso está acontecendo seus cenários escolhidos usando zombarias, mas seu teste original ainda é válido e, como estamos verificando apenas o comportamento / estado observável, ele não precisa ser alterado. Você acaba com 3 testes:

  1. Meu resultado está classificado?
  2. Dado um cenário (digamos que a lista inicial esteja quase ordenada para começar), é feita uma chamada para a classe que classifica seqüências de caracteres usando o algoritmo X?
  3. Dado um cenário (a lista inicial está em uma ordem aleatória), é feita uma chamada para a classe que classifica seqüências de caracteres usando o algoritmo Y?

A alternativa seria começar a testar o código privado dentro da sua classe - você não ganha nada com isso - os testes acima me dizem tudo o que preciso saber no que diz respeito ao teste de unidade. Ao adicionar testes particulares, você está construindo uma jaqueta reta, quanto mais trabalho seria se você não apenas verificasse se o resultado foi classificado, mas também como é classificado?

Os testes (desse tipo) só devem mudar quando o comportamento mudar, começar a escrever testes em relação ao código privado e sair pela janela.

FinnNk
fonte
1
Talvez haja um mal-entendido sobre o significado de "privado". Em nosso sistema, 99% do código é "privado"; então, temos uma pequena API para automatizar / controlar remotamente um dos componentes do sistema. Quero dizer unidade testando o código de todos os outros módulos.
Wizard79
4

aqui está outro motivo: no caso hipotético, eu teria que escolher entre testar a unidade da API externa e as partes privadas, escolheria as partes privadas.

Se todas as partes privadas forem cobertas por um teste, a API que consiste dessas partes privadas também deverá ser coberta por quase 100%, com exceção da camada superior. Mas é provável que seja uma camada fina.

Por outro lado, ao testar apenas a API, pode ser realmente difícil cobrir completamente todos os caminhos de código possíveis.

stijn
fonte
+1 "por outro lado ..." Mas, se nada mais, adicione testes onde uma falha seria mais prejudicial.
Tony Ennis
2

É difícil fazer as pessoas aceitarem o teste de unidade porque parece uma perda de tempo ("poderíamos estar codificando outro projeto de ganhar dinheiro!") Ou recursivo ("E então temos que escrever casos de teste para os casos de teste!") Eu sou culpado de dizer os dois.

A primeira vez que você encontra um bug, precisa encarar a verdade de que não é perfeito (com que rapidez os programadores esquecem!) E você diz "Hmmm".


Outro aspecto do teste de unidade é que o código deve ser escrito para ser testável. Perceber que Some Code é facilmente testável e Some Code não é faz com que um bom programador fique "Hmmm".


Você perguntou ao seu colega de trabalho por que o teste de unidade era útil apenas para APIs externas?


Uma maneira de mostrar o valor do teste de unidade é esperar que um bug desagradável aconteça e depois mostrar como o teste de unidade poderia ter evitado isso. Isso não é para esfregar na cara deles, é para, na mente deles, mover o teste de unidade de uma Teoria da Torre de Marfim para uma realidade nas trincheiras.

Outra maneira é esperar até que o mesmo erro ocorra duas vezes . "Uhhh, bom chefe, nós adicionamos código para testar um nulo após o problema da semana passada, mas o usuário inseriu uma coisa vazia dessa vez!"


Lidere pelo exemplo. Escreva testes de unidade para o SEU código e mostre o valor ao seu chefe. Então veja se o chefe vai pedir pizza para almoçar um dia e fazer uma apresentação.


Finalmente, não posso dizer o alívio que sinto quando estamos prestes a pressionar e recebo uma barra verde nos testes de unidade.

Tony Ennis
fonte
2

Existem dois tipos de código privado: código privado que é chamado pelo código público (ou código privado que é chamado pelo código privado que é chamado pelo código público (ou ...)) e código privado que não é eventualmente chamado pelo público código.

O primeiro já é testado através dos testes do código público. Este último não pode ser chamado em tudo e, portanto, deve ser suprimido, não testada.

Observe que, quando você faz TDD, é impossível a existência de código privado não testado.

Jörg W Mittag
fonte
Em nosso sistema, 99% do código é do terceiro tipo : privado, não chamado pelo código público e essencial para o sistema (apenas uma parte mínima do nosso sistema possui uma API pública externa).
Wizard79
1
"Observe que, quando você faz TDD, é impossível a existência de código privado não testado." <- exclui um caso de teste, sem saber que esse é o único teste a cobrir uma ramificação específica. OK, é mais um código "atualmente não testado", mas é fácil ver uma refatoração trivial posterior alterando esse código ... apenas o seu conjunto de testes não o cobre mais.
Frank Shearar
2

Teste de unidade é tudo sobre o teste de unidades do seu código. Cabe a você definir o que é uma unidade. Seus colegas de trabalho definem unidades como elementos da API.

De qualquer forma, testar a API também deve resultar no exercício de código privado. Se você definir a cobertura do código como um indicador do progresso do teste de unidade, você terminará testando todo o seu código. Se alguma parte do código não foi alcançada, dê três opções aos seus colegas de trabalho:

  • definir outro caso de teste para cobrir essa parte,
  • analisar o código para justificar por que não pode ser abordado no contexto de testes de unidade, mas deve ser abordado em outras situações,
  • remova o código morto que não foi coberto nem justificado.
mouviciel
fonte
Em nosso sistema, a API é apenas uma parte mínima, que permite automação / controle remoto para aplicativos de terceiros. Testando apenas as contas da API para uma cobertura de código de 1% ...
Wizard79