Por que os testes de unidade com falha são vistos como ruins?


93

Em algumas organizações, aparentemente, parte do processo de lançamento do software é usar o teste de unidade, mas a qualquer momento todos os testes de unidade devem passar. Por exemplo, pode haver alguma tela que mostre todos os testes de unidade que passam em verde - o que deve ser bom.

Pessoalmente, acho que não é assim que deve ser pelas seguintes razões:

  1. Promove a ideia de que o código deve ser perfeito e não deve haver erros - o que no mundo real é certamente impossível para um programa de qualquer tamanho.

  2. É desincentivo pensar em testes de unidade que falharão. Ou certamente criar testes de unidade que seriam difíceis de corrigir.

  3. Se a qualquer momento todos os testes de unidade forem aprovados, não haverá uma imagem geral do estado do software em nenhum momento. Não há roteiro / objetivo.

  4. Ele impede a gravação de testes de unidade antecipadamente - antes da implementação.

Eu até sugeriria que nem mesmo a liberação de software com falha nos testes de unidade não é necessária. Pelo menos, você sabe que algum aspecto do software tem limitações.

Estou faltando alguma coisa aqui? Por que as organizações esperam que todos os testes de unidade sejam aprovados? Isso não é viver em um mundo de sonhos? E isso realmente não impede um entendimento real do código?


Comentários não são para discussão prolongada; esta conversa foi movida para o bate-papo .
maple_shaft

Respostas:


270

Esta pergunta contém vários conceitos errados do IMHO, mas o principal sobre o qual gostaria de focar é que ele não diferencia entre ramos de desenvolvimento local, ramos de tronco, preparo ou liberação.

Em um ramo de desenvolvimento local, é provável que haja alguns testes de unidade com falha quase a qualquer momento. No porta-malas, isso só é aceitável até certo ponto, mas já é um forte indicador para consertar as coisas o mais rápido possível. Observe que a falha nos testes de unidade no porta-malas pode atrapalhar o resto da equipe, pois eles exigem que todos verifiquem se a última alteração não está causando a falha.

Em uma ramificação de teste ou versão, os testes que falham são "alerta vermelho", mostrando que houve algo completamente errado com algum conjunto de alterações, quando foi mesclado do tronco para a ramificação de versão.

Eu até sugeriria que nem mesmo a liberação de software com falha nos testes de unidade não é necessária.

A liberação de software com alguns erros conhecidos abaixo de uma certa gravidade não é necessariamente ruim. No entanto, essas falhas conhecidas não devem causar um teste de unidade com falha. Caso contrário, após cada teste de unidade, será necessário analisar os 20 testes de unidade com falha e verificar um por um se a falha foi aceitável ou não. Isso fica complicado, propenso a erros e descarta grande parte do aspecto de automação dos testes de unidade.

Se você realmente tiver testes para erros conhecidos e aceitáveis, use o recurso de desativar / ignorar da sua ferramenta de teste de unidade (para que eles não sejam executados por padrão, apenas sob demanda). Além disso, adicione um ticket de baixa prioridade ao rastreador de problemas, para que o problema não seja esquecido.


18
Eu acho que essa é a resposta real. O OP menciona "processo de liberação" e "alguma tela [mostrando resultados de teste]", que soa como um servidor de compilação. Lançamento não é o mesmo que desenvolvimento (não desenvolva na produção!); é bom ter testes com falha no dev, eles são como TODOs; todos devem estar verdes (CONCLUÍDOS) quando enviados ao servidor de compilação.
Warbo

7
Uma resposta muito melhor do que a mais votada. Ele mostra uma compreensão de onde a operação está vindo sem lecioná-los sobre uma situação ideal do mundo, reconhece a possibilidade de erros conhecidos (para os quais nem todo o roteiro foi descartado para corrigir alguns casos raros de esquina) e explica que os testes de unidade devem definitivamente seja verde em uma ramificação / processo de liberação.
Sebastiaan van den Broek

5
@SebastiaanvandenBroek: obrigado pela sua resposta positiva. Só para deixar isso claro: os testes de unidade com falha no IMHO devem ser raros, mesmo no porta-malas, já que a obtenção de tais falhas com muita frequência perturbará toda a equipe, não apenas quem fez a alteração que causou a falha.
Doc Brown

4
Acho que o problema aqui é pensar que todos os testes automatizados são testes de unidade. Muitas estruturas de teste incluem a capacidade de marcar testes que se espera que falhem (geralmente chamados de XFAIL). (Isso é diferente de um teste que requer um resultado de erro. Os testes XFAIL seriam idealmente bem-sucedidos, mas não o fazem.) O conjunto de testes ainda passa com essas falhas. O caso de uso mais comum são coisas que apenas falham em algumas plataformas (e são apenas XFAIL naquelas), mas o uso do recurso para rastrear algo que exigirá muito trabalho para corrigir agora também está dentro do razoável. Mas esses tipos de testes geralmente não são testes de unidade.
Kevin Cathcart

1
+1, embora eu sugira uma pequena adição (em negrito) a esta frase: "Isso torna complicado, propenso a erros, condiciona as pessoas a ignorar falhas no conjunto de testes como ruído e descarta grande parte do aspecto de automação dos testes de unidade .
mtraceur

228

... todos os testes de unidade são aprovados em verde - o que deve ser bom.

Isso é bom Não "deveria ser" sobre isso.

Promove a ideia de que o código deve ser perfeito e não deve haver erros - o que no mundo real é certamente impossível para um programa de qualquer tamanho.

Não. Isso prova que você testou o código da melhor maneira possível até esse ponto. É perfeitamente possível que seus testes não abranjam todos os casos. Nesse caso, quaisquer erros aparecerão nos relatórios de erros e você escreverá testes [com falha] para reproduzir os problemas e, em seguida, corrigirá o aplicativo para que os testes passem.

É desincentivo pensar em testes de unidade que falharão.

Testes com falha ou negativos impõem limites firmes ao que seu aplicativo aceitará e não aceitará. A maioria dos programas que conheço se opõe a uma "data" de 30 de fevereiro. Além disso, os desenvolvedores, tipos criativos que somos, não querem quebrar "seus bebês". O foco resultante em casos de "caminho feliz" leva a aplicativos frágeis que quebram - frequentemente.

Para comparar a mentalidade do desenvolvedor e do testador:

  • Um desenvolvedor para assim que o código faz o que ele deseja.
  • Um testador para quando não consegue mais quebrar o código.

Essas são perspectivas radicalmente diferentes e difíceis de conciliar para muitos desenvolvedores.

Ou certamente criar testes de unidade que seriam difíceis de corrigir.

Você não escreve testes para fazer o trabalho por si mesmo. Você escreve testes para garantir que seu código esteja fazendo o que deveria fazer e, mais importante, que continue fazendo o que deveria fazer depois de alterar sua implementação interna.

  • A depuração "prova" que o código faz o que você deseja hoje .
  • Os testes "provam" que o código ainda faz o que você deseja ao longo do tempo .

Se a qualquer momento todos os testes de unidade forem aprovados, não haverá uma imagem geral do estado do software em nenhum momento. Não há roteiro / objetivo.

O único teste de "imagem" fornece um instantâneo em que o código "funciona" no momento em que foi testado. Como evolui depois disso é uma história diferente.

Ele impede a gravação de testes de unidade antecipadamente - antes da implementação.

É exatamente o que você deveria estar fazendo. Escreva um teste que falhe (porque o método que está testando ainda não foi implementado) e, em seguida, escreva o código do método para fazer o método funcionar e, portanto, o teste passar. Esse é basicamente o ponto crucial do desenvolvimento orientado a testes.

Eu até sugeriria que nem mesmo a liberação de software com falha nos testes de unidade não é necessária. Pelo menos, você sabe que algum aspecto do software tem limitações.

Liberar código com testes interrompidos significa que parte de sua funcionalidade não funciona mais como antes. Isso pode ser um ato deliberado porque você corrigiu um bug ou aprimorou um recurso (mas, em seguida, deveria ter alterado o teste primeiro para que falhasse, depois codificou a correção / aprimoramento, fazendo o teste funcionar no processo). Mais importante: somos todos humanos e cometemos erros. Se você quebrar o código, você deve interromper os testes e esses testes quebrados devem definir os alarmes tocando.

Isso não é viver em um mundo de sonhos?

Se qualquer coisa, ele está vivendo no mundo real , reconhecendo que os desenvolvedores não são nem onisciente nem infallable, que fazem cometer erros e que precisamos de uma rede de segurança para nos pegar, se e quando fazer asneira!
Digite testes.

E isso realmente não impede um entendimento real do código?

Possivelmente. Você não precisa necessariamente entender a implementação de algo para escrever testes para ele (isso faz parte do objetivo deles). Os testes definem o comportamento e os limites do aplicativo e garantem que eles permaneçam os mesmos, a menos que você os altere deliberadamente.


7
@ Tibos: Desabilitar um teste é como comentar uma função. Você tem controle de versão. Use-o.
22418 Kevin

6
@ Kevin Eu não sei o que você quer dizer com 'usá-lo'. Eu marco um teste como 'ignorado' ou 'pendente' ou qualquer outra convenção que meu executor de teste usa e comprometo essa tag de salto no controle de versão.
Dcorking 22/05/19

4
@dcorking: Quero dizer, não comente o código, exclua-o. Se, posteriormente, você decidir que precisa, restaure-o do controle de versão. Cometer um teste desativado não é diferente.
22418 Kevin

4
"É perfeitamente possível que seus testes não abranjam todos os casos". Eu diria até agora que, para cada pedaço de código não trivial testado, você definitivamente não tem todos os casos cobertos.
CorsiKa

6
Os proponentes do @Tibos do teste de unidade dizem que o tempo de ciclo entre a escrita de um teste com falha e o código do código deve ser pequeno (por exemplo, 20 minutos. Alguns reivindicam 30 segundos). Se você não tiver tempo para escrever o código imediatamente, provavelmente é muito complexo. Se não for complexo, exclua o teste, pois ele pode ser reescrito se o recurso descartado for adicionado novamente. Por que não comentar? Você não sabe que o recurso será adicionado novamente novamente; portanto, o teste (ou código) comentado é apenas ruído.
CJ Dennis

32

Por que os testes de unidade com falha são vistos como ruins?

Eles não são - o desenvolvimento orientado a testes baseia-se na noção de falha nos testes. Falha nos testes de unidade para impulsionar o desenvolvimento, falha nos testes de aceitação para impulsionar uma história ....

O que você está perdendo é o contexto ; onde os testes de unidade podem falhar?

A resposta usual é que os testes de unidade podem falhar apenas em caixas de proteção privadas.

A noção básica é a seguinte: em um ambiente em que os testes com falha são compartilhados, é necessário um esforço extra para entender se uma alteração no código de produção introduziu um novo erro. A diferença entre zero e não zero é muito mais fácil de detectar e gerenciar do que a diferença entre N e não N.

Além disso, manter o código compartilhado limpo significa que os desenvolvedores podem permanecer na tarefa. Quando mesclo seu código, não preciso mudar os contextos do problema que estou sendo pago para resolver, para calibrar minha compreensão de quantos testes devem falhar. Se o código compartilhado estiver passando em todos os testes, todas as falhas que aparecerem quando eu mesclar minhas alterações deverão fazer parte da interação entre o meu código e a linha de base limpa existente.

Da mesma forma, durante o embarque, um novo desenvolvedor pode se tornar produtivo mais rapidamente, pois não precisa gastar tempo descobrindo quais testes com falha são "aceitáveis".

Para ser mais preciso: a disciplina é que os testes executados durante a compilação devem passar.

Não há nada de errado em testar os testes que estão desabilitados .

Por exemplo, em um ambiente de "integração contínua", você compartilha o código em uma cadência alta. Integrar frequentemente não significa necessariamente que suas alterações precisam estar prontas para lançamento. Há uma variedade de técnicas de implantação escura que impedem que o tráfego seja liberado em seções do código até que estejam prontos.

Essas mesmas técnicas também podem ser usadas para desativar os testes com falha.

Um dos exercícios pelos quais passei em um lançamento pontual foi lidar com o desenvolvimento de um produto com muitos testes com falha. A resposta que encontramos foi simplesmente percorrer o conjunto, desabilitando os testes que falharam e documentando cada um. Isso nos permitiu chegar rapidamente a um ponto em que todos os testes habilitados estavam passando, e o gerenciamento / doador de objetivos / proprietário do ouro puderam ver quais as negociações que havíamos feito para chegar a esse ponto e tomar decisões informadas sobre limpeza versus novo trabalho.

Resumindo: existem outras técnicas para rastrear o trabalho que não foram realizadas além de deixar um monte de testes com falha no pacote em execução.


Eu teria dito "Não ... nada de errado em ter falhando testes que são pessoas com deficiência ".
CJ Dennis

Essa mudança certamente esclarece o significado. Obrigado.
VoiceOfUnreason

26

Existem muitas respostas excelentes, mas eu gostaria de acrescentar outro ângulo que acredito que ainda não esteja bem coberto: qual é exatamente o ponto de fazer testes.

Os testes de unidade não existem para verificar se o seu código está livre de erros.

Eu acho que esse é o principal equívoco. Se esse fosse o papel deles, você realmente esperaria ter testes reprovados em todo o lugar. Mas ao invés,

Os testes de unidade verificam se o seu código faz o que você pensa.

Em casos extremos, pode incluir verificar se os erros conhecidos não são corrigidos. O objetivo é ter controle sobre sua base de código e evitar alterações acidentais. Quando você faz uma alteração, tudo bem e, na verdade, espera-se que você faça alguns testes - você está alterando o comportamento do código. O teste recentemente quebrado é agora uma boa trilha do que você mudou. Verifique se todas as quebras estão em conformidade com o que você deseja com sua alteração. Nesse caso, basta atualizar os testes e continuar. Caso contrário - bem, seu novo código é definitivamente com erros, volte e corrija-o antes de enviá-lo!

Agora, todas as opções acima funcionam apenas se todos os testes forem verdes, resultando em fortes resultados positivos: é exatamente assim que o código funciona. Testes vermelhos não têm essa propriedade. "Isto é o que este código não faz" raramente é uma informação útil.

Testes de aceitação podem ser o que você está procurando.

Há testes de aceitação. Você pode escrever um conjunto de testes que precisam ser cumpridos para chamar o próximo marco. Estes são ok para ser vermelho, porque é para isso que eles foram projetados. Mas eles são muito diferentes dos testes de unidade e nem podem nem devem substituí-los.


2
Uma vez tive que substituir uma biblioteca por outra. Os testes de unidade me ajudaram a garantir que todos os casos de canto ainda fossem tratados de forma idêntica pelo novo código.
Thorbjørn Ravn Andersen

24

Eu o vejo como o equivalente em software da síndrome da janela quebrada .

Testes de trabalho me dizem que o código é de uma determinada qualidade e que os proprietários do código se preocupam com ele.

Quanto a quando você deve se preocupar com a qualidade, isso depende bastante de qual filial / repositório de código-fonte você está trabalhando. O código do desenvolvedor pode muito bem ter testes interrompidos indicando trabalho em andamento (espero!).

Testes interrompidos em uma ramificação / repositório para um sistema ativo devem definir imediatamente os alarmes tocando. Se os testes interrompidos continuarem falhando ou se estiverem permanentemente marcados como "ignorar", espere que o número deles aumente com o tempo. Se estes não forem revisados ​​regularmente, o precedente terá sido configurado para que os testes quebrados sejam deixados.

Os testes quebrados são vistos de maneira pejorativa em muitas lojas, a fim de restringir a possibilidade de comprometimento do código quebrado .


9
Se os testes documentam a maneira como um sistema é, eles certamente sempre devem passar - se não forem, isso significa que os invariantes estão quebrados. Mas se eles documentarem a forma como um sistema deveria ser, testes com falha também podem ser utilizados - desde que sua estrutura de testes de unidade ofereça uma boa maneira de marcá-los como "problemas conhecidos" e se você os vincular a um item no rastreador de problemas. Eu acho que ambas as abordagens têm seu mérito.
Luaan

1
@Lanan Sim, isso supõe que todos os testes de unidade sejam criados igualmente. Certamente não é incomum os gerentes de compilação dividirem e dividirem os testes por meio de algum atributo, dependendo de quanto tempo eles são executados, quão frágeis são e vários outros critérios.
Robbie Dee #

Essa resposta é ótima pela minha própria experiência. Depois que algumas pessoas se acostumam a ignorar vários testes com falha ou a quebrar as práticas recomendadas em alguns pontos, espere alguns meses e você verá% de testes ignorados aumentando dramaticamente, a qualidade do código caindo para o nível "hack-script" . E será muito difícil lembrar todos do processo.
usr-local-

11

Aqui está a falácia lógica subjacente:

Se estiver bom quando todos os testes forem aprovados, será ruim se algum teste falhar.

Com testes de unidade, é bom quando todos os testes passam. Também é bom quando um teste falha. Os dois não precisam estar em oposição.

Um teste com falha é um problema detectado por suas ferramentas antes de atingir um usuário. É uma oportunidade de corrigir um erro antes de ser publicado. E isso é uma coisa boa.


Linha de pensamento interessante. Vejo a falácia da pergunta mais ou menos assim: "como é bom quando um teste de unidade falha, é ruim quando todos os testes passam".
Doc Brown

Embora o seu último parágrafo seja um bom argumento, parece que o problema é mais um mal-entendido de "a qualquer momento todos os testes de unidade devem passar" (como a resposta aceita indica) e o ponto dos testes de unidade.
Dukeling 23/05

9

A resposta de Phill W é ótima. Eu não posso substituí-lo.

No entanto, quero me concentrar em outra parte que pode ter sido parte da confusão.

Em algumas organizações, aparentemente, parte do processo de liberação do software é usar o teste de unidade, mas a qualquer momento todos os testes de unidade devem passar

"a qualquer momento" está exagerando seu caso. O importante é que os testes de unidade sejam aprovados após uma certa alteração ser implementada, antes de você começar a implementar outra alteração.
É assim que você monitora quais alterações causaram um erro. Se os testes de unidade começaram a falhar após a implementação da alteração 25, mas antes de implementar a alteração 26, você sabe que a alteração 25 causou o erro.

Durante a implementação de uma mudança, é claro que os testes de unidade podem falhar; depende muito de quão grande é a mudança. Se estou desenvolvendo um recurso principal, que é mais do que apenas um pequeno ajuste, provavelmente vou fazer os testes por um tempo até concluir a implementação da minha nova versão da lógica.


Isso pode criar conflitos quanto às regras da equipe. Na verdade, eu encontrei isso algumas semanas atrás:

  • Cada confirmação / envio causa uma compilação. A construção nunca deve falhar (se ocorrer ou algum teste falhar, o desenvolvedor responsável é o culpado).
  • É esperado que todos os desenvolvedores enviem suas alterações (mesmo que incompletas) no final do dia, para que os líderes da equipe possam revisar o código pela manhã.

Qualquer regra ficaria bem. Mas ambas as regras não podem funcionar juntas. Se me for atribuída uma alteração importante que leva vários dias para ser concluída, não seria possível seguir as duas regras ao mesmo tempo. A menos que eu comente minhas alterações todos os dias e apenas as comente sem comentários depois que tudo estiver pronto; que é apenas um trabalho sem sentido.

Nesse cenário, o problema aqui não é que os testes de unidade não tenham propósito; é que a empresa tem expectativas irreais . Seu conjunto de regras arbitrário não abrange todos os casos, e a falha em seguir as regras é cegamente considerada como falha do desenvolvedor, em vez de falha da regra (que é, no meu caso).


3
A única maneira que isso pode funcionar é usar a ramificação, de modo que os desenvolvedores se comprometam e enviem para destacar ramificações que não precisam ser compiladas de maneira limpa enquanto incompletas, mas que se comprometam com a ramificação principal que desencadeia uma compilação, que deve compilar de maneira limpa.
Gwyn Evans

1
Impor forçar mudanças incompletas é um absurdo, não vejo justificativa para isso. Por que não revisar o código quando a alteração estiver concluída?
Callum Bradbury

Bem, por um lado, é uma maneira rápida de garantir que o código não esteja apenas no laptop / estação de trabalho do desenvolvedor se o disco rígido parar de funcionar ou se perder de outra forma - se houver uma política de confirmação, mesmo no meio do trabalho, então há uma quantidade limitada de trabalho em risco.
Gwyn Evans

1
Os sinalizadores de recursos corrigem o aparente paradoxo.
precisa

1
@ Flater sim, também para refazer a lógica existente.
23418 RubberDuck

6

Se você não corrigir todos os testes de unidade, poderá entrar rapidamente no estado em que ninguém corrige nenhum teste quebrado.

  1. Está incorreto, pois passar nos testes de unidade não mostra que o código é perfeito

  2. É um desincentivo criar código que também seria difícil de testar, o que é bom do ponto de vista do design

  3. A cobertura do código pode ajudar lá (embora não seja uma panacéia). Os testes de unidade também são apenas um aspecto do teste - você também deseja testes de integração / aceitação.


6

Para adicionar alguns pontos às respostas já boas ...

mas a qualquer momento todos os testes de unidade devem passar

Isso mostra uma falta de entendimento de um processo de liberação. Uma falha no teste pode indicar um recurso planejado no TDD que ainda não foi implementado; ou pode indicar um problema conhecido que tem uma correção planejada para uma versão futura; ou pode ser simplesmente algo em que a gerência decidiu que isso não é importante o suficiente para corrigir, porque é improvável que os clientes percebam. A principal coisa que todos compartilham é que a administração fez um julgamento sobre a falha.

Promove a ideia de que o código deve ser perfeito e não deve haver erros - o que no mundo real é certamente impossível para um programa de qualquer tamanho.

Outras respostas cobriram os limites dos testes.

Não entendo por que você acha que eliminar bugs é uma desvantagem. Se você não deseja entregar o código que você verificou (da melhor maneira possível), faz o que deveria, por que você está trabalhando em software?

Se a qualquer momento todos os testes de unidade forem aprovados, não haverá uma imagem geral do estado do software em nenhum momento. Não há roteiro / objetivo.

Por que deve haver um roteiro?

Os testes de unidade inicialmente verificam se a funcionalidade funciona, mas depois (como testes de regressão) verificam se você não quebrou nada inadvertidamente. Para todos os recursos com testes de unidade existentes, não há roteiro . Sabe-se que todos os recursos funcionam (dentro dos limites dos testes). Se esse código for concluído, ele não terá um roteiro, pois não há necessidade de mais trabalho nele.

Como engenheiros profissionais, precisamos evitar a armadilha do revestimento de ouro. Os entusiastas do hobby podem perder tempo mexendo nas bordas com algo que funciona. Como profissionais, precisamos entregar um produto. Isso significa que conseguimos algo funcionando, verificamos que está funcionando e passamos ao próximo trabalho.


6

Promove a ideia de que o código deve ser perfeito e não deve haver erros - o que no mundo real é certamente impossível para um programa de qualquer tamanho.

Não é verdade. por que você acha que é impossível? aqui exemplo para o programa que funciona:

public class MyProgram {
  public boolean alwaysTrue() {
    return true;
  }

  @Test
  public void testAlwaysTrue() {
    assert(alwaysTrue() == true);
  }
}

É desincentivo pensar em testes de unidade que falharão. Ou certamente criar testes de unidade que seriam difíceis de corrigir.

Nesse caso, pode não ser teste de unidade, mas teste de integração se for complicado

Se a qualquer momento todos os testes de unidade forem aprovados, não haverá uma imagem geral do estado do software em nenhum momento. Não há roteiro / objetivo.

verdade, é chamado teste de unidade por um motivo, verifica uma pequena unidade de código.

Ele impede a gravação de testes de unidade antecipadamente - antes da implementação.

Desenvolvedores vaideter a escrita de quaisquer testes se eles não entenderem seus benefíciospor natureza (a menos que sejam provenientes do controle de qualidade)


“Os desenvolvedores irão impedir [sic] de escrever todos os testes por natureza” - isso é totalmente absurdo. Eu trabalho em uma empresa inteira de desenvolvedores que praticam TDD e BDD.
precisa

@RubberDuck Tentei responder a um "fato" em questão e estava exagerando. Vou atualizar
user7294900

"X será dissuadido de fazer Y se eles não entenderem os benefícios de Y" se aplica a praticamente qualquer X e Y, portanto essa afirmação provavelmente não é particularmente útil. Provavelmente faria mais sentido explicar os benefícios de escrever os testes e, especificamente, fazê-lo antecipadamente.
Dukeling 23/05

2
"impossível para um programa de qualquer tamanho" não significa "todos os programas, independentemente do tamanho", significa "qualquer programa significativo (com um comprimento não trivial)". Sua tentativa de contra-exemplo é inaplicável, porque não é ' um programa significativo e útil.
Ben Voigt

@BenVoigt Acho que não devo dar um "programa significativo" como resposta.
User7294900

4

Promove a ideia de que o código deve ser perfeito e não deve haver erros

Definitivamente não. Promove a ideia de que seus testes não devem falhar, nada mais e nada menos. Supondo que fazer testes (mesmo muitos deles) diga algo sobre "perfeito" ou "sem erros" é uma falácia. Decidir quão superficial ou profundo seus testes devem ser é uma parte significativa da criação de bons testes, e a razão pela qual temos categorias distintas de testes (testes "unitários", testes de integração, "cenários" no sentido do pepino etc.).

É desincentivo pensar em testes de unidade que falharão. Ou certamente criar testes de unidade que seriam difíceis de corrigir.

No desenvolvimento orientado a testes, é obrigatório que todos os testes de unidade falhem primeiro, antes de começar a codificar. É chamado "ciclo vermelho-verde" (ou "ciclo vermelho-verde-refator") por esse mesmo motivo.

  • Sem o teste falhar, você não sabe se o código é realmente testado pelo teste. Os dois podem não estar relacionados.
  • Alterando o código para fazer exatamente o teste passar de vermelho para verde, nada mais e nada menos, você pode estar bastante confiante de que seu código faz o que deve fazer e não muito mais (do que você talvez nunca precise).

Se a qualquer momento todos os testes de unidade forem aprovados, não haverá uma imagem geral do estado do software em nenhum momento. Não há roteiro / objetivo.

Os testes são mais um tipo de micro-objetivo. No desenvolvimento orientado a teste, o programador escreverá um teste (singular) primeiro e depois terá um objetivo claro de implementar algum código; depois o próximo teste e assim por diante.

A função dos testes não deve estar completa antes de o código ser escrito.

Quando feito corretamente, em um idioma e com uma biblioteca de testes adequada a essa abordagem, isso pode acelerar enormemente o desenvolvimento, pois as mensagens de erro (exceções / traços de pilha) podem apontar diretamente o desenvolvedor para onde ele precisa executar o trabalho Próximo.

Ele impede a gravação de testes de unidade antecipadamente - antes da implementação.

Não vejo como essa afirmação seria verdadeira. Idealmente, os testes de escrita devem fazer parte da implementação.

Estou faltando alguma coisa aqui? Por que as organizações esperam que todos os testes de unidade sejam aprovados?

Porque as organizações esperam que os testes tenham relevância para o código. Escrever testes com êxito significa que você documentou parte de seu aplicativo e provou que o aplicativo faz o que (o teste) diz. Nada mais e nada menos.

Além disso, uma grande parte dos testes é a "regressão". Você deseja desenvolver ou refatorar novo código com confiança. Ter uma grande quantidade de testes ecológicos permite fazer isso.

Isso vai do organizacional ao nível psicológico. Um desenvolvedor que sabe que seus erros serão provavelmente detectados pelos testes terá muito mais liberdade para encontrar soluções inteligentes e ousadas para os problemas que ele precisa resolver. Por outro lado, um desenvolvedor que não tiver testes, depois de algum tempo, ficará paralisado (devido ao medo), porque ele nunca sabe se uma alteração que faz interrompe o restante do aplicativo.

Isso não é viver em um mundo de sonhos?

Não. Trabalhar com um aplicativo orientado a testes é pura alegria - a menos que você não goste do conceito por qualquer motivo ("mais esforço" etc. etc.) que possamos discutir em outra pergunta.

E isso realmente não impede um entendimento real do código?

Absolutamente não, por que faria?

Você encontra muitos projetos de código aberto grandes (para os quais o gerenciamento de "entendimento" e conhecimento sobre o código é um tópico muito urgente) que realmente usam os testes como a principal documentação do software, além de serem testes, também fornece exemplos reais, funcionais e sintaticamente corretos para usuários ou desenvolvedores do aplicativo / biblioteca. Isso geralmente funciona esplendidamente.

Obviamente, escrever testes ruins é ruim. Mas isso não tem nada a ver com a função dos testes em si.


3

(Dos meus comentários originais)

Há uma diferença entre a funcionalidade necessária e as metas futuras. Os testes são para a funcionalidade necessária: são precisos, formais, executáveis ​​e, se falharem, o software não funcionará. Objetivos futuros podem não ser precisos ou formais, muito menos executáveis, por isso é melhor deixá-los em linguagem natural, como em rastreadores de problemas / bugs, documentação, comentários etc.

Como exercício, tente substituir a frase "teste de unidade" na sua pergunta por "erro do compilador" (ou "erro de sintaxe", se não houver compilador). É óbvio que uma versão não deve ter erros de compilador, pois seria inutilizável; no entanto, erros de compilador e erros de sintaxe são o estado normal das coisas na máquina de um desenvolvedor quando eles escrevem código. Os erros só desaparecem quando terminam; e é exatamente quando o código deve ser enviado. Agora substitua "erro do compilador" neste parágrafo por "teste de unidade" :)


2

O objetivo dos testes automatizados é informar quando você quebrou algo o mais cedo possível . O fluxo de trabalho se parece um pouco com isso:

  1. Faça uma mudança
  2. Crie e teste suas alterações (idealmente automaticamente)
  3. Se os testes falharem, significa que você quebrou algo que funcionava anteriormente
  4. se os testes forem aprovados, você deve ter certeza de que sua alteração não introduziu novas regressões (dependendo da cobertura do teste)

Se seus testes já estavam falhando, a etapa 3 não funciona com tanta eficácia - os testes falharão, mas você não sabe se isso significa que você quebrou algo ou não sem investigar. Talvez você possa contar o número de testes com falha, mas uma alteração pode corrigir um erro e interromper outro, ou um teste pode começar a falhar por um motivo diferente. Isso significa que você precisa aguardar um pouco para saber se algo foi quebrado, até que todos os problemas tenham sido corrigidos ou até que cada teste com falha seja investigado.

A capacidade dos testes de unidade encontrarem erros recém-introduzidos o mais cedo possível é a coisa mais valiosa sobre os testes automatizados - quanto mais tempo um defeito for descoberto, mais caro será o reparo.

Promove a ideia de que o código deve ser perfeito e não deve haver bugs.
É desincentivo pensar em testes de unidade que falharão

Os testes de coisas que não funcionam não dizer nada - escrever testes de unidade para as coisas que fazem o trabalho, ou que você está prestes a corrigir. Isso não significa que seu software está livre de defeitos, significa que nenhum dos defeitos para os quais você escreveu anteriormente testes de unidade voltaram novamente.

Impede a gravação de testes de unidade antecipadamente

Se funcionar para você, escreva os testes antecipadamente, apenas não os verifique no seu mestre / tronco até que eles passem.

Se a qualquer momento todos os testes de unidade forem aprovados, não haverá uma imagem geral do estado do software em nenhum momento. Não há roteiro / objetivo.

Os testes de unidade não são para definir um roteiro / objetivo, talvez usar um backlog para isso? Se todos os seus testes forem aprovados, o "panorama geral" é que o software não está quebrado (se a cobertura do teste for boa). Bem feito!


2

As respostas existentes são certamente boas, mas não vi ninguém abordar esse equívoco fundamental na pergunta:

a qualquer momento todos os testes de unidade devem passar

Não. Certamente, isso não será verdade. Enquanto desenvolvo software, o NCrunch costuma ser marrom (falha na compilação) ou vermelho (falha no teste).

Onde o NCrunch precisa ser verde (todos os testes sendo aprovados) é quando eu estou pronto para enviar uma confirmação ao servidor de controle de origem, porque nesse momento outros podem ter uma dependência do meu código.

Isso também alimenta o tópico de criação de novos testes: os testes devem afirmar a lógica e o comportamento do código. Condições de contorno, condições de falha etc. Quando escrevo novos testes, tento identificar esses "pontos de acesso" no código.

Os testes de unidade documentam como eu espero que meu código seja chamado - condições prévias, resultados esperados etc.

Se um teste for interrompido após uma alteração, preciso decidir se o código ou o teste está com erro.


Como uma observação lateral, o teste de unidade às vezes anda de mãos dadas com o Test Driven Development. Um dos princípios do TDD é que os testes quebrados são suas orientações. Quando um teste falha, você precisa corrigir o código para que o teste seja aprovado. Aqui está um exemplo concreto do início desta semana:

Antecedentes : escrevi e agora suporte uma biblioteca usada por nossos desenvolvedores que é usada para validar consultas Oracle. Tivemos testes que afirmavam que a consulta correspondia a algum valor esperado, o que tornava importante o caso (não está no Oracle) e aprovava alegremente consultas inválidas, desde que correspondessem completamente ao valor esperado.

Em vez disso, minha biblioteca analisa a consulta usando Antlr e uma sintaxe Oracle 12c e, em seguida, agrupa várias asserções na própria árvore de sintaxe. Coisas como, é válido (nenhum erro de análise foi gerado), todos os seus parâmetros são satisfeitos pela coleção de parâmetros, todas as colunas esperadas lidas pelo leitor de dados estão presentes na consulta, etc. Todos esses itens foram transferidos para produção em vários momentos.

Um dos meus colegas engenheiros me enviou uma consulta na segunda-feira que falhou (ou melhor, teve sucesso quando deveria ter falhado) no fim de semana. Minha biblioteca disse que a sintaxe estava correta, mas explodiu quando o servidor tentou executá-la. E quando ele olhou para a consulta, ficou óbvio o porquê:

UPDATE my_table(
SET column_1 = 'MyValue'
WHERE id_column = 123;

Carreguei o projeto e adicionei um teste de unidade que afirmava que essa consulta não deveria ser válida. Obviamente, o teste falhou.

Em seguida, depurei o teste que falhou, passei pelo código onde esperava que ele lançasse a exceção e descobri que o Antlr estava gerando um erro no ponto aberto, mas não da maneira que o código anterior esperava. Modifiquei o código, verifiquei que o teste agora estava verde (passando) e que nenhum outro havia quebrado no processo, confirmado e enviado por push.

Isso levou cerca de 20 minutos e, no processo, eu aprimorei significativamente a biblioteca, porque agora ela suportava toda uma gama de erros que anteriormente estava ignorando. Se eu não tivesse testes de unidade para a biblioteca, pesquisar e corrigir o problema poderia levar horas.


0

Um ponto que acho que não sai das respostas anteriores é que há uma diferença entre testes internos e testes externos (e acho que muitos projetos não são cuidadosos o suficiente para distinguir os dois). Um teste interno testa se algum componente interno está funcionando como deveria; um teste externo mostra que o sistema como um todo está funcionando da maneira que deveria. É bem possível, é claro, ter falhas nos componentes que não resultam em falha do sistema (talvez haja um recurso do componente que o sistema não use, ou talvez o sistema se recupere de uma falha do sistema). componente). Uma falha de componente que não resulte em falha do sistema não deve impedi-lo de liberar.

Vi projetos paralisados ​​por ter muitos testes de componentes internos. Toda vez que você tenta implementar uma melhoria de desempenho, você quebra dezenas de testes, porque está alterando o comportamento dos componentes sem realmente alterar o comportamento visível externamente do sistema. Isso leva a uma falta de agilidade no projeto como um todo. Acredito que o investimento em testes de sistemas externos geralmente tem um retorno muito melhor do que o investimento em testes de componentes internos, especialmente quando você está falando de componentes de nível muito baixo.

Quando você sugere que falhas nos testes de unidade não importam realmente, me pergunto se é isso que você tem em mente. Talvez você deva avaliar o valor dos testes de unidade e abandonar aqueles que causam mais problemas do que valem, enquanto se concentra mais em testes que verificam o comportamento visível externamente do aplicativo.


Eu acho que o que você está descrevendo como "testes externos" é frequentemente descrito em outro lugar como testes de "integração".
GalacticCowboy

Sim, mas me deparei com diferenças na terminologia. Para algumas pessoas, o teste de integração é mais sobre a configuração de software / hardware / rede implantada, enquanto eu estou falando sobre o comportamento externo de um pedaço de software que você está desenvolvendo.
Michael Kay

0

"mas a qualquer momento todos os testes de unidade devem passar"

Se essa é a atitude da sua empresa, isso é um problema. Em um determinado momento, a saber, quando declaramos que o código está pronto para passar para o próximo ambiente, todos os testes de unidade devem passar. Mas durante o desenvolvimento, devemos esperar rotineiramente que muitos testes de unidade falhem.

Nenhuma pessoa razoável espera que um programador obtenha seu trabalho perfeito na primeira tentativa. O que razoavelmente esperamos é que ele continue trabalhando até que não haja problemas conhecidos.

"É um desincentivo pensar em testes de unidade que falharão. Ou certamente criar testes de unidade que seriam difíceis de corrigir". Se alguém em sua organização pensa que não deve mencionar um teste possível, pois pode falhar e fazer com que trabalhe mais para corrigi-lo, essa pessoa é totalmente desqualificada para o trabalho. Essa é uma atitude desastrosa. Você gostaria de um médico que dissesse: "Quando estou fazendo uma cirurgia, eu deliberadamente não verifico se os pontos estão corretos, porque se eu ver que eles não estão, terei que voltar e refazê-los e isso desacelerará a conclusão da operação "?

Se a equipe é hostil aos programadores que identificam erros antes que o código seja produzido, você tem um problema real com a atitude dessa equipe. Se a gerência punir os programadores que identificam erros que atrasam a entrega, é provável que a sua empresa vá à falência.

Sim, certamente é verdade que às vezes pessoas racionais dizem: "Estamos nos aproximando do prazo, este é um problema trivial e não vale a pena dedicar os recursos agora que seriam necessários para corrigi-lo". Mas você não pode tomar essa decisão racionalmente se não souber. Examinar com calma uma lista de erros e atribuir prioridades e agendas para corrigi-los é racional. Deliberadamente tornar-se ignorante dos problemas, para que você não precise tomar essa decisão é uma tolice. Você acha que o cliente não descobrirá apenas porque você não queria saber?


-7

Este é um exemplo específico de viés de confirmação , no qual as pessoas tendem a buscar informações que confirmam suas crenças existentes.

Um exemplo famoso dessa ocorrência está no jogo 2,4,6.

  • Eu tenho uma regra na minha cabeça que qualquer série de três números passará ou falhará,
  • 2,4,6 é um passe
  • você pode listar conjuntos de três números e eu direi se eles passam ou falham.

A maioria das pessoas escolhe uma regra, diz "o intervalo entre o 1º e o 2º número é o mesmo que o intervalo entre o 2º e o 3º".

Eles testarão alguns números:

  • 4, 8, 12? Passar
  • 20, 40, 60? Passar
  • 2, 1004, 2006? Passar

Eles dizem "Sim, toda observação confirma minha hipótese, deve ser verdadeira". E anuncie seu governo à pessoa que está dando o enigma.

Mas eles nunca receberam um único 'fracasso' em nenhum conjunto de três números. A regra poderia ser apenas "os três números precisam ser números" para todas as informações que eles realmente têm.

A regra é apenas que os números estão em ordem crescente. As pessoas normalmente só acertam esse enigma se testam falhas. A maioria das pessoas erra, escolhendo uma regra mais específica e testando apenas números que atendem a essa regra específica.

Quanto ao motivo pelo qual as pessoas caem no viés de confirmação e podem ver os testes de unidade falhando como evidência de um problema, existem muitos psicólogos que podem explicar o viés de confirmação melhor do que eu, basicamente se resume a pessoas que não gostam de estar erradas e lutam para realmente tentar para provar que estão errados.


2
Como isso é relevante para a questão? Testes de unidade com falha são evidências de um problema, por definição.
Frax

1
Você pode absolutamente fazer com que os testes de unidade que exijam que o sistema em teste entre no modo de falha. Isso não é o mesmo que nunca ver um teste falhar. É também por isso que TDD é especificado como um ciclo "Vermelho-> Verde-> Refatorador"
Caleth
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.