Discussão sobre Testes Unitários

Participo da Lista Dojo Bahia e hoje tivemos uma discussão de altíssimo nível e que considerei muito interessante. A discussão surgiu após um colega perguntar como ele poderia testar os métodos privados de sua classe. Minha opinião é que não se deve testar métodos privados! Mas há pessoas que pensam diferente. Vejam abaixo a transcrição desta conversa, sem a identificação dos nomes. Não coloquei todas mensagens, pois nem todas tocaram no cerne do assunto. Também removi algumas partes das mensagens para não tornar o post muito chato! Você pensa diferente de todos nós? Então, diga qual a sua opinião nos comentários!

Mike começa a conversa com seu ponto de vista, que é parecido com o meu:
Veja bem, você deve fazer teste unitários de métodos de acesso publico e não de privados. Normalmente os métodos private servem de “ajudantes” dos métodos públicos. Se você testar os métodos publicos que tem ligação com o método private você terá testado ele. Mas aí terá um dilema do teste unitário, que deveria testa o método unicamente sem dependência.


Minha vez de opinar:
Métodos privados são “ajudantes de pedreiro”. Você testa se o pedreiro está fazendo tudo certo, o que, em teoria, deveria garantir que o ajudante está fazendo sua parte corretamente. Ou seja, métodos privados não existem para serem testados, apenas os públicos.

Testes unitários testam o comportamento de um objeto e o comportamento de um objeto está exposto por sua interface pública (se é que faz sentido dizer “interface privada”). Se os métodos desta interface pública delegam suas tarefas para métodos privados, para o teste unitário pouco importa.


Nemo pensa diferente de mim e de Mike, perceba:
Permita-me discordar: a qualidade do método privado vai influenciar diretamente na qualidade do objeto como um todo. Não interessa como você fatore a solução do problema. O código é todo seu, ou da equipe.

Quem faz teste unitário não é quem “compra” o objeto mas quem “faz”. Para o dono da obra, realmente o que importa é o trabalho do pedreiro, mas ele, como chefe de sua equipe, vai querer saber se os ajudantes tem capacidade ou não. E nessa analogia, você é o dona da obra e o pedreiro, ao mesmo tempo.


Não concordei com Nemo e resolvi escrever uma Bíblia para mostrar exatamente o que penso:

Entendo seu ponto de vista, mas bato na mesma tecla: conceitualmente, testes unitários não são feitos para testarem métodos privados. Vamos a outra analogia. Não sei se você conhece o Giga de Testes. Trabalhei na Semp Toshiba e lá havia um projeto para criar um hardware que testa hardware (parecido com nossos testes unitários, software que testa software).

A ideia é simples. Você conecta vários fios em uma placa mãe em suas saídas externas: porta usb, saída de vídeo, saída de mouse, saída de teclado… e por aí vai. A partir daí, aperta-se um botão e a Giga realiza todos os testes destas saídas, emitindo sinais e aguardando que os sinais corretos saiam. Perceba que não existe teste da transmissão entre cada transistor da placa mãe. Seria extremamente custoso. E muito desnecessário também. Basta saber se a saída está correta, conforme todas as entradas possíveis, que basta para a Giga de Testes.

Em software, para mim, é a mesma coisa. Para mim, importa testar todas as entradas possíveis de uma interface de um objeto. Se todas as entradas possíveis geram as respostas esperadas, para mim pouco importa o funcionamento interno… se há 200 métodos privados para resolver o problema? Pouco importa, desde que a interface externa me traga o resultado correto.

Testes unitários não existem para garantir que uma classe foi bem projetada, bem escrita. Aí já entram outras disciplinas do desenvolvimento de sistemas. O que defendo é esta diferença básica que não pode ser confundida. Nesta ideia, métodos privados não são “testáveis”. Se há métodos privados desnecessários, testes unitários não devem tratar isto. Se um método privado retorna o resultado errado para um método público que o chama, mas mesmo assim o método público retorna o resultado correto para quem o chama, então o teste unitário deve passar sem problemas e seu objetivo foi concluído com sucesso.

Agora deve-se partir para outras disciplinas que vão verificar se a classe foi bem projetada, se aquele método privado que está lá realmente tem necessidade de existir. Se o retorno errado dele não existe exatamente porque o método privado não precisa existir e coisas abstratas deste tipo. 🙂


Nemo agora me entendeu:
Acho que o problema de entendimento foi este: minha concepção de teste unitário não é a sua. Você considerou o objeto como a unidade, mas há quem, como eu, considere até métodos como importantes o suficiente para serem testados. Na minha opinião, os testes têm que me ajudar em todo meu código


Oba! Mais gente na conversa:
Como foi citado aqui, quem decide a menor unidade a ser testada é a equipe, ao depender do projeto em relação a custos x
benefícios. E eu vejo de forma clara que se você não testar o teste privado de forma independente, o seu código está sujeito à falhas.

Normalmente quando desenvolvemos uma classe, temos em mente, principalmente, os conceitos de coesão e acoplamento. Então criamos internamente métodos que auxiliem os métodos públicos. Por conta da reutilização de código, um método privado pode ser usado por vários outros e se esse conter um condicional, o seu fluxo vai ser diferente em cada contexto. Dessa forma, você vai precisar garantir em cada método público que você venha, que ele teste todas as possibilidades do teste privado. E isso deixa os testes espalhados, dificultando o controle e trazendo, muitas vezes, duplicação de teste, pois muitas vezes você precisa efetuar alguns testes antes para então testar o fluxo diferente ou então pela complexidade de se verificar em todos os pontos o que já foi e o que não foi testado. E quanto melhor é o seu código, pior para testar, uma vez que para diminuir o tamanho dos métodos e distribuir responsabilidades, você acaba criando método privado chamado por um método privado que é chamado por outro privado.

Se o seu sistema é pequeno, isso é até fácil de controlar, mas a medida que ele for crescendo, essa tarefa vai se tornando mais complicada. E no momento de uma possível manutenção, ai que “o bicho pega”.


Olha eu, chatão, de novo:

Aí estamos com o mesmo problema que ocorre com a Giga de Testes. É preciso testar a corrente que passa de um transistor para outro? Ou, para a venda do equipamento, o importante é que a resposta a um clique no mouse tenha o retorno correto, mesmo que por vias “tortas”? Por questões de custos e viabilidade, é melhor garantir que a resposta é correta. Depois outra disciplina da engenharia tratará da qualidade.

O teste UNITÁRIO é para garantir que uma UNIDADE trabalha da forma como ela foi especificada. Se você tem um método somar2mais2(), deve existir um teste que garanta que o retorno é apenas 4. Se internamente este método chama outros cinco privados, pouco importa para o teste unitário. Os métodos privados existem para auxiliar os públicos a concluírem um trabalho que é seu.

Normalmente, eu crio toda a lógica do meu método sem separar em privados. Faço os testes e garanto que está tudo correto (eu sei, nada de TDD aqui, né! Foi mal!). Depois, usando a ideia de baby steps, eu refatoro meu método dividindo-o, se necessário, em métodos privados. Volto a testar para ver se continua funcionando. Mais uma vez refatoro para melhorar a qualidade do código. Novamente faço testes e por aí vai.

Porventura, mais tarde eu crio um novo método. Percebo que posso repassar uma parte do seu código para um método privado que já existe. Mas preciso mudar um pouco o comportamento deste método… e mudo! Caso esta mudança tenha criado algum problema, ela será apontada nos testes já escritos para os métodos públicos que o usam. Caso não, ótimo! Pronto, só preciso disto.

O funcionamento do meu objeto está garantido. Quem usá-lo, pode ficar tranquilo que a resposta continuará sendo a mesma, mesmo que o método privado tenha sido alterado ou que dezenas de outros tenham sido criados. Para quem usa o objeto, o que importa é a resposta que ele dá, e não o que ele faz para dar a resposta e testes unitários existem para isto. Se foi mal implementado, aí são outros 500! Aí estamos falando de Qualidade e testes unitários não podem e não precisam garantir qualidade, mas garantir que o comportamento de um objeto está conforme sua especificação. Somar2Mais2 nunca poderia retornar 5, e o teste unitário deve garantir apenas isto.