Primeiro e menos grave, ter uma cobertura de testes alta dá uma falsa sensação de segurança. Se temos 80% de cobertura, então 80% do código não tem erros, certo? Não exatamente. O que estamos medindo é cobertura de comandos, que é a forma mais fraca de cobertura. Eis um exemplo simples:
if ((a > 0) || (b < 0)) { c = 0; } else { c = 1; }
Bastam dois testes, um com a==0 e outro com a==1 para cobrirmos todas as linhas. No entanto, se houver um erro na segunda cláusula (se tivesse de ser b > 0, por exemplo), nossos testes não detectarão o erro. Um testador experiente sabe que tem de testar as duas cláusulas, mas um inexperiente pode ser ludibriado pela medida de 100% de cobertura desse código e pensar que não há nada mais a fazer.
Segundo e mais grave, há testes que simplesmente não fazem sentido. Já vi inúmeros testes em que o testador está simplesmente repetindo todo o comportamento da classe sendo testada. Quando se usa frameworks como EasyMock ou Mockito, fica ainda mais fácil cometer esse erro. Esses frameworks nos permitem verificar se determinados métodos estão sendo chamados com certos parâmetros. Se só o que o método faz é chamar os métodos x, y e z, o que acaba acontecendo é que o teste unitário simplesmente verifica se x, y e z são chamados nessa ordem. Qual é o erro que pode ser encontrado por esse teste unitário? Nenhum. Só o que acontece é que quem modificar o método vai ter de modificar seu clone do mal nos testes unitários.
Imagine uma classe de modelo cujo objetivo é receber estruturas de dados internas e convertê-las em chamadas ao banco de dados. Como testar essa classe? Fazendo um mock do banco, chamando a classe com alguns valores concretos, e observando que o banco está sendo chamado com os valores corretos. Qual o valor de se fazer esse teste? Quase nenhum. O único valor é que você está efetivamente duplicando a funcionalidade que você fez na classe original e, se as duas implementações forem diferentes, você vai ter de olhar pra ver qual das duas está errada. É uma versão do fenômeno da segunda vez.
Quando faz sentido fazer testes de unidade, então? Pra mim, só quando a classe tem lógica interna, ou seja, toma decisões ou faz alguma computação real que pode ser observada externamente. O ideal é poder observar as mudanças no estado no sistema, e não o seu fluxo de controle. Traduzindo:
int metodo(int a) { int b = w(a); z(b); if (b > 0) { return x(b); } else { return y(b); } }
Como deve ser um teste para esse método? Deve ser um teste caixa-preta. O teste deve testar os valores retornados são compatíveis com diferentes valores de entrada de a. Nada mais. Para isso precisamos entender o que w(a), x(b) e y(b) retornam. E z(b)? Como sempre é executado, não faz sentido testá-lo. E devemos verificar se x(b) ou y(b) foram chamados? Na minha opinião, não. Devemos é saber se os valores de retorno esperados são equivalentes aos recebidos.
O bom teste deve desafiar o código sendo testado. Deve criar várias situações distintas e observar se o código reage de acordo com o desejado. Quanto mais forte o desafio, mais útil o teste. Desafios fracos criam testes fracos.
E daí, onde quero chegar com tudo isso? daí que criar e manter testes tem um custo, e esse custo não é trivial. Vamos então à lei de Torsten sobre testes unitários:
Todo teste unitário deve trazer valor a longo prazo maior que seu custo de criação e manutenção.Aqui, o valor do teste é proporcional ao desafio que ele faz ao código testado.
Resumindo, é OK não chegar nem perto de 100% de cobertura. Teste apenas o que faz sentido, lembre-se que nem todo teste é útil. E quando fizer sentido, teste direito, não se esqueça de testar nenhum caso importante só porque já cobriu todos os comandos.