Escrevemos perto de 3.000 testes - os dados foram codificados, com pouca reutilização de código. Essa metodologia começou a nos morder na bunda. À medida que o sistema muda, nos vemos gastando mais tempo consertando testes quebrados. Temos testes unitários, de integração e funcionais.
O que estou procurando é uma maneira definitiva de escrever testes gerenciáveis e de manutenção.
Frameworks
.net
unit-testing
Chuck Conway
fonte
fonte
Respostas:
Não pense neles como "testes de unidade quebrados", porque não são.
São especificações que o seu programa não oferece mais suporte.
Não pense nisso como "corrigindo os testes", mas como "definindo novos requisitos".
Os testes devem especificar seu aplicativo primeiro, e não o contrário.
Você não pode dizer que possui uma implementação funcional até saber que funciona. Você não pode dizer que funciona até testá-lo.
Algumas outras notas que podem guiá-lo:
fonte
Don't think of it as "fixing the tests", but as "defining new requirements".
O que você descreve pode não ser tão ruim, mas um indicador de problemas mais profundos que seus testes descobrem
Se você pudesse alterar seu código e seus testes não fossem interrompidos, isso seria suspeito para mim. A diferença entre uma mudança legítima e um bug é apenas o fato de ser solicitada, e o que é solicitado é (TDD assumido) definido pelos seus testes.
Dados codificados em testes são uma coisa boa. Os testes funcionam como falsificações, não como provas. Se houver muito cálculo, seus testes podem ser tautologias. Por exemplo:
Quanto maior a abstração, mais você se aproxima do algoritmo e, com isso, mais perto de comparar a implementação acutal a si mesma.
A melhor reutilização de código nos testes é imho 'Checks', como nas jUnits
assertThat
, porque eles mantêm os testes simples. Além disso, se os testes puderem ser refatorados para compartilhar código, o código real testado provavelmente também pode ser , reduzindo assim os testes àqueles que testam a base refatorada.fonte
Eu tive esse problema também. Minha abordagem aprimorada foi a seguinte:
Não escreva testes de unidade, a menos que sejam a única maneira de testar alguma coisa.
Estou totalmente preparado para admitir que os testes de unidade têm o menor custo de diagnóstico e tempo para correção. Isso os torna uma ferramenta valiosa. O problema é que, com o óbvio que sua milhagem pode variar, os testes de unidade geralmente são muito pequenos para merecer o custo de manutenção da massa do código. Eu escrevi um exemplo na parte inferior, dê uma olhada.
Use asserções onde quer que sejam equivalentes ao teste de unidade para esse componente. As asserções têm a propriedade legal de que elas sempre são verificadas em qualquer compilação de depuração. Portanto, em vez de testar as restrições da classe "Employee" em uma unidade de testes separada, você está efetivamente testando a classe Employee em todos os casos de teste do sistema. As asserções também têm a boa propriedade de que não aumentam a massa do código tanto quanto os testes de unidade (que eventualmente requerem andaimes / zombarias / qualquer outra coisa).
Antes que alguém me mate: as construções de produção não devem colidir com afirmações. Em vez disso, eles devem fazer logon no nível "Erro".
Como precaução para alguém que ainda não pensou nisso, não afirme nada nas entradas do usuário ou da rede. É um grande erro ™.
Nas minhas últimas bases de código, removi criteriosamente os testes de unidade sempre que vejo uma oportunidade óbvia de afirmações. Isso reduziu significativamente o custo de manutenção geral e me tornou uma pessoa muito mais feliz.
Prefira testes de sistema / integração, implementando-os para todos os seus fluxos primários e experiências do usuário. Os casos de canto provavelmente não precisam estar aqui. Um teste do sistema verifica o comportamento no final do usuário executando todos os componentes. Por isso, um teste do sistema é necessariamente mais lento, então escreva os que importam (nem mais, nem menos) e você encontrará os problemas mais importantes. Os testes do sistema têm uma sobrecarga de manutenção muito baixa.
É importante lembrar que, como você está usando asserções, cada teste do sistema executará algumas centenas de "testes de unidade" ao mesmo tempo. Você também tem certeza de que os mais importantes são executados várias vezes.
Escreva APIs fortes que possam ser testadas funcionalmente. Os testes funcionais são desajeitados e (vamos ser sinceros) meio que sem sentido se sua API dificultar a verificação dos componentes funcionais por conta própria. Bom design de API a) simplifica as etapas de teste eb) gera afirmações claras e valiosas.
O teste funcional é a coisa mais difícil de acertar, especialmente quando você tem componentes que se comunicam um para muitos ou (pior ainda, oh, Deus) muitos para muitos através das barreiras do processo. Quanto mais entradas e saídas conectadas a um único componente, mais difícil é o teste funcional, porque você precisa isolar um deles para realmente testar sua funcionalidade.
Na questão "não escreva testes de unidade", apresentarei um exemplo:
O escritor deste teste adicionou sete linhas que não contribuem nada para a verificação do produto final. O usuário nunca deve ver isso acontecendo, porque a) ninguém deve passar NULL lá (então escreva uma afirmação) ou b) o caso NULL deve causar um comportamento diferente. Se o caso for (b), escreva um teste que realmente verifique esse comportamento.
Minha filosofia tornou-se que não devemos testar artefatos de implementação. Devemos apenas testar qualquer coisa que possa ser considerada uma saída real. Caso contrário, não há como evitar escrever duas vezes a massa básica de código entre os testes de unidade (que forçam uma implementação específica) e a própria implementação.
É importante observar, aqui, que existem bons candidatos para testes de unidade. De fato, existem até várias situações em que um teste de unidade é o único meio adequado para verificar algo e em que é de alto valor escrever e manter esses testes. Do topo da minha cabeça, esta lista inclui algoritmos não triviais, contêineres de dados expostos em uma API e código altamente otimizado que parece "complicado" (também conhecido como "o próximo cara provavelmente estragará tudo").
Meu conselho específico para você, então: comece a excluir testes de unidade criteriosamente conforme eles quebram, fazendo a si mesmo a pergunta: "isso é uma saída ou estou desperdiçando código?" Você provavelmente conseguirá reduzir o número de coisas que estão desperdiçando seu tempo.
fonte
Parece-me que o seu teste de unidade funciona como um encanto. É bom que seja tão frágil às mudanças, já que esse é o ponto principal. Pequenas alterações nos testes de quebra de código para que você possa eliminar a possibilidade de erro em todo o programa.
No entanto, lembre-se de que você realmente só precisa testar as condições que poderiam fazer com que seu método falhasse ou desse resultados inesperados. Isso manteria sua unidade testando mais propenso a "quebrar" se houver um problema genuíno, em vez de coisas triviais.
Embora me pareça que você está reprojetando fortemente o programa. Nesses casos, faça o que for necessário e remova os testes antigos e substitua-os por novos depois. Reparar testes de unidade só vale a pena se você não estiver consertando devido a mudanças radicais no seu programa. Caso contrário, você pode achar que está dedicando muito tempo para reescrever testes para ser aplicável na sua seção recém-escrita do código do programa.
fonte
Estou certo de que outras pessoas terão muito mais informações, mas, na minha experiência, estas são algumas coisas importantes que o ajudarão:
fonte
Manuseie testes como você faz com o código-fonte.
Controle de versão, lançamentos de pontos de verificação, rastreamento de problemas, "propriedade de recursos", planejamento e estimativa de esforços, etc. etc.
fonte
Você definitivamente deve dar uma olhada nos padrões de teste XUnit de Gerard Meszaros . Possui uma ótima seção com muitas receitas para reutilizar seu código de teste e evitar duplicação.
Se seus testes são frágeis, também pode ser que você não recorra o suficiente para fazer o dobro. Especialmente, se você recriar gráficos inteiros de objetos no início de cada teste de unidade, as seções Organizar em seus testes poderão ficar grandes demais e você poderá se encontrar em situações em que precisará reescrever as seções Organizar em um número considerável de testes apenas porque uma das classes mais usadas foi alterada. Zombarias e stubs podem ajudá-lo aqui, reduzindo o número de objetos que você precisa reidratar para ter um contexto de teste relevante.
Tirar os detalhes sem importância de suas configurações de teste por meio de zombarias e stubs e aplicar padrões de teste para reutilizar o código deve reduzir significativamente sua fragilidade.
fonte