Se o seu código de teste de unidade "cheira", isso realmente importa?

52

Normalmente, eu apenas faço meus testes de unidade juntos, usando copiar e colar e todo tipo de outras práticas ruins. Os testes de unidade geralmente acabam ficando feios, cheios de "cheiro de código", mas isso realmente importa? Eu sempre digo a mim mesma, contanto que o código "real" seja "bom", isso é tudo o que importa. Além disso, o teste de unidade geralmente requer vários "hackers fedorentos", como funções de stubbing.

Quão preocupado devo estar com testes de unidade mal projetados ("fedorentos")?

Botões840
fonte
8
Quão difícil pode ser corrigir o código que você está sempre copiando?
JeffO 18/05
7
o cheiro do código nos testes pode facilmente ser um indicador do cheiro oculto no restante do código - por que abordar os testes como se eles não fossem um código "real"?
HorusKol

Respostas:

75

Os cheiros dos testes de unidade são importantes? Sim definitivamente. No entanto, eles são diferentes dos odores de código porque os testes de unidade têm uma finalidade diferente e têm um conjunto diferente de tensões que informam seu design. Muitos odores no código não se aplicam a testes. Dada minha mentalidade de TDD, eu argumentaria que os cheiros de teste de unidade são mais importantes que os cheiros de código, porque o código está lá apenas para satisfazer os testes.

Aqui estão alguns cheiros comuns de teste de unidade:

  • Fragilidade : seus testes falham com frequência e inesperadamente, mesmo para alterações de código aparentemente triviais ou não relacionadas?
  • Vazamento de estado : seus testes falham de maneira diferente, dependendo, por exemplo, de que ordem eles são executados?
  • Inchaço da instalação / desmontagem: Os seus blocos de instalação / desmontagem são longos e crescem mais? Eles realizam algum tipo de lógica de negócios?
  • Tempo de execução lento : seus testes levam muito tempo para serem executados? Algum dos seus testes unitários individuais demora mais de um décimo de segundo para ser executado? (Sim, estou falando sério, um décimo de segundo.)
  • Atrito : Os testes existentes dificultam a criação de novos testes? Você se depara com falhas nos testes com frequência durante a refatoração?

A importância dos cheiros é que eles são indicadores úteis de design ou outras questões mais fundamentais, como "onde há fumaça, há fogo". Não procure apenas odores de teste, procure também a causa subjacente.

Por outro lado, aqui estão algumas boas práticas para testes de unidade:

  • Feedback rápido e focado : seus testes devem isolar a falha rapidamente e fornecer informações úteis sobre sua causa.
  • Minimizar a distância do código de teste : deve haver um caminho claro e curto entre o teste e o código que o implementa. Longas distâncias criam loops de feedback desnecessariamente longos.
  • Teste uma coisa de cada vez : testes de unidade devem testar apenas uma coisa. Se você precisar testar outra coisa, escreva outro teste.
  • Um bug é um teste que você esqueceu de escrever : O que você pode aprender com essa falha em escrever testes melhores e mais completos no futuro?
Rein Henrichs
fonte
2
"Os testes de unidade têm uma finalidade diferente e têm um conjunto diferente de tensões que informam seu design". Por exemplo, eles devem DAMP, não necessariamente SECO.
Jörg W Mittag
3
@ Jörg concordou, mas DAMP realmente significa alguma coisa? : D
Rein Henrichs
5
@ Rein: Frases descritivas e significativas. Além disso, não completamente SECO. Veja codeshelter.wordpress.com/2011/04/07/…
Robert Harvey
+1. Eu também não sabia o significado de DAMP.
Egarcia #
2
@ironcode Se você não pode trabalhar em um sistema isolado, sem medo de interromper sua integração com outros sistemas, isso me parece um forte acoplamento. Esse é precisamente o objetivo de identificar cheiros de teste como um longo tempo de execução: eles informam sobre problemas de design, como acoplamento rígido. A resposta não deve ser "Ah, então esse cheiro de teste é inválido", deve ser "O que esse cheiro me diz sobre o meu design?" Os testes de unidade que especificam a interface externa do seu sistema devem ser suficientes para informar se suas alterações interrompem ou não a integração com seus consumidores.
precisa
67

Estar preocupado. Você escreve testes de unidade para provar que seu código age da maneira que você espera. Eles permitem refatorar rapidamente com confiança. Se seus testes forem frágeis, difíceis de entender ou difíceis de manter, você ignorará os testes com falha ou os desativará à medida que sua base de código evoluir, negando muitos dos benefícios de escrever testes.

jayraynet
fonte
17
+1 No momento em que você começa a ignorar os testes reprovados, eles não agregam valor.
19

Acabei de ler The Art of Unit Testing alguns dias atrás. O autor defende colocar tanto cuidado em seus testes de unidade quanto o código de produção.

Eu experimentei testes mal escritos e inatingíveis em primeira mão. Eu escrevi alguns dos meus. É virtualmente garantido que, se um teste for um pé no saco, ele não será mantido. Uma vez que os testes estão fora de sincronia com o código testado, eles se tornam ninhos de mentiras e enganos. O objetivo principal dos testes de unidade é incutir confiança de que não quebramos nada (isto é, eles criam confiança). Se os testes não podem ser confiáveis, são piores que inúteis.

Joshua Smith
fonte
4
+1 você tem que manter os testes, assim como o código de produção
Hamish Smith
Outro livro muito bom sobre esse tópico é "Padrões de teste xUnit - Refatcoring test code" de Gerard Meszaros.
Adrianboimvaser 19/05/11
7

Desde que seu teste de unidade realmente teste seu código na "maioria" dos casos. (Disse de propósito, já que às vezes é difícil encontrar todos os resultados possíveis). Eu acho que o código "fedorento" é sua preferência pessoal. Eu sempre escrevo o código de uma maneira que eu possa lê-lo e entendê-lo em alguns segundos, em vez de vasculhar o lixo e tentar entender o que é o quê. Especialmente quando você volta a isso depois de um período significativo de tempo.

Conclusão - seus testes, supõe-se que seja fácil. Você não quer se confundir com código "fedorento".

Lucas
fonte
5
+1: Não se preocupe com o código do teste de unidade. Algumas das perguntas mais estúpidas giram em torno de tornar o código de teste de unidade mais "robusto" para que uma alteração na programação não interrompa o teste de unidade. Bobagem. Os testes de unidade são projetados para serem quebradiços. Tudo bem se eles são menos robustos que o código do aplicativo que foram projetados para testar.
S.Lott
11
@ S.Lott Ambos concordam e discordam, por razões melhor explicadas na minha resposta.
Rein Henrichs
11
@ Rein Henrichs: esses são cheiros muito, muito mais sérios do que os descritos na pergunta. "copiar e colar" e "parecer feio" não parecem tão ruins quanto o cheiro que você descreve quando os testes não são confiáveis.
S.Lott
11
@ S.Lott exatamente, acho que, se vamos falar sobre "cheiros de teste de unidade", é crucial fazer essa distinção. Cheiros de código em testes de unidade geralmente não são. Eles são escritos para diferentes propósitos, e as tensões que os fazem mal em um contexto são muito diferentes no outro.
Rein Henrichs
2
@ S. Lott btw esta parece ser uma oportunidade perfeita para usar o recurso de bate-papo muito negligenciada :)
Rein Henrichs
6

Definitivamente. Algumas pessoas dizem que "qualquer teste é melhor do que nenhum teste". Eu discordo totalmente - testes mal escritos reduzem o tempo de desenvolvimento e você acaba perdendo dias corrigindo testes "quebrados" porque, em primeiro lugar, eles não eram bons testes de unidade. Para mim, no momento, as duas coisas em que estou focado para tornar meus testes valiosos, e não um fardo, são:

Manutenção

Você deve testar o resultado (o que acontece), não o método ( como acontece). Sua configuração para o teste deve ser o mais dissociada possível da implementação: configure apenas resultados para chamadas de serviço etc. que sejam absolutamente necessários.

  • Use uma estrutura de simulação para garantir que seus testes não dependam de nada externo
  • Favorecer stubs sobre zombarias (se sua estrutura distinguir entre eles) sempre que possível
  • Não há lógica nos testes! Ifs, switches, for-eaches, cases, try-catchs etc. são todos ruins, pois podem introduzir bugs no próprio código de teste

Legibilidade

Não há problema em permitir um pouco mais de repetição em seus testes, o que você normalmente não permitiria em seu código de produção, se os tornar mais legíveis. Basta equilibrar isso com o material de manutenção acima. Seja explícito no que o teste está fazendo!

  • Tente manter um estilo de "organizar, agir, afirmar" para seus testes. Isso separa sua configuração e expectativas do cenário, da ação que está sendo executada e do resultado que está sendo afirmado.
  • Mantenha uma asserção lógica por teste (se o nome do seu teste tiver "e" nele, pode ser necessário dividi-lo em vários testes)

Em conclusão, você deve se preocupar muito com os testes "fedorentos" - eles podem acabar perdendo seu tempo, sem fornecer valor.

Você disse:

O teste de unidade geralmente requer vários "hacks fedorentos", como funções de stubbing.

Parece que você definitivamente poderia ler algumas técnicas de teste de unidade, como usar uma estrutura de zombaria, para tornar sua vida muito mais fácil. Eu recomendaria muito fortemente The Art of Unit Testing , que cobre o exposto e muito mais. Achei isso esclarecedor depois de lutar por muito tempo com testes mal escritos, inatingíveis e "fedorentos". É um dos melhores investimentos de tempo que fiz este ano!

mjhilton
fonte
Excelente resposta. Eu tenho apenas uma pequena queixa: muito mais importante do que "uma asserção lógica por teste" é uma ação por teste. O ponto não importa quantas etapas sejam necessárias para organizar um teste, deve haver apenas uma "ação do código de produção em teste". Se a ação mencionada tiver mais de um efeito colateral, é possível que você tenha várias asserções.
Desiludido
5

Duas perguntas para você:

  • Você está absolutamente certo de que está testando o que acha que está testando?
  • Se alguém olhar para o teste de unidade, será capaz de descobrir o que o código deve fazer ?

Existem maneiras de lidar com tarefas repetitivas em seus testes de unidade, que geralmente são códigos de configuração e desmontagem. Essencialmente, você tem um método de configuração de teste e um método de desmontagem de teste - todas as estruturas de teste de unidade têm suporte para isso.

Os testes de unidade devem ser pequenos e fáceis de entender. Se não estiverem, e o teste falhar, como você solucionará o problema em um período razoavelmente curto. Facilite para si mesmo por alguns meses depois, quando precisar voltar ao código.

Berin Loritsch
fonte
5

Quando o lixo começa a cheirar, é hora de retirá-lo. Seu código de teste deve ser tão limpo quanto o seu código de produção. Você mostraria para sua mãe?

Bill Leeper
fonte
3

Além das outras respostas aqui.

O código de baixa qualidade nos testes de unidade não se restringe ao seu conjunto de testes de unidade.

Uma das funções desempenhadas pelo teste de unidade é a documentação.

O conjunto de testes de unidade é um dos locais em que se procura descobrir como uma API deve ser usada.

É improvável que os chamadores da sua API copiem partes do seu conjunto de testes de unidade, levando o código incorreto do conjunto de testes a infectar o código ativo em outro local.

Paul Butcher
fonte
3

Quase não enviei minha resposta, pois demorei um pouco para descobrir como abordar isso como uma pergunta legítima.

As razões pelas quais os programadores se envolvem nas "melhores práticas" não são por razões estéticas ou porque desejam um código "perfeito". É porque economiza tempo para eles.

  • Isso economiza tempo, porque é mais fácil ler e entender um bom código.
  • Isso economiza tempo porque, quando você precisa encontrar uma correção, é mais fácil e rápido localizar.
  • Isso economiza tempo porque, quando você deseja estender o código, é mais fácil e rápido fazê-lo.

Portanto, a pergunta que você faz (do meu ponto de vista) é: devo economizar tempo ou escrever um código que desperdice meu tempo?

Para essa pergunta, só posso dizer: qual a importância do seu tempo?

FYI: stubbing, mocking e monkey patching todos têm usos legítimos. Eles apenas "cheiram" quando seu uso não é apropriado.

dietbuddha
fonte
2

Tento especificamente NÃO fazer meus testes de unidade muito robustos. Eu já vi testes de unidade que começam a manipular erros em nome de serem robustos. Você termina com testes que engolem os bugs que eles estão tentando capturar. Os testes de unidade também precisam fazer algumas coisas estranhas com bastante frequência para fazer as coisas funcionarem. Pense em toda a idéia de acessadores particulares usando reflexão ... se eu visse um monte de pessoas em todo o código de produção, ficaria preocupado 9 vezes em 10. Acho que mais tempo deve ser gasto para pensar no que está sendo testado , em vez de limpeza de código. De qualquer forma, os testes precisarão ser alterados com bastante frequência, se você fizer alguma refatoração importante, então por que não apenas juntá-los, ter um pouco menos de propriedade e estar mais motivado a reescrevê-los ou retrabalhá-los quando chegar a hora?

Morgan Herlocker
fonte
2

Se você optar por uma cobertura pesada e de baixo nível, estará gastando tanto tempo ou mais no código de teste quanto no código do produto ao fazer modificações sérias.

Dependendo da complexidade da configuração do teste, o código pode ser mais complexo. (httpcontext.current vs. o monstro horrível de tentar criar um falso com precisão, por exemplo)

A menos que seu produto seja algo em que você raramente faça uma alteração nas interfaces existentes e que suas entradas no nível da unidade sejam muito simples de configurar, eu estaria pelo menos tão preocupado com a compreensão dos testes quanto com o produto real.

Conta
fonte
0

Os odores de código são apenas um problema no código que precisa ser alterado ou compreendido em algum momento posterior.

Então, acho que você deve corrigir os odores do código quando precisar "se aproximar" do teste de unidade especificado, mas não antes.

Ian
fonte