É necessário manter testes para funções simples (independentes)?


36

Considere isto:

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

Suponha que você escreva vários testes para a função acima e prove para si e para os outros que "funciona".

Por que não remover esses testes e viver feliz para sempre? O que quero dizer é que algumas funções não precisam ser testadas continuamente após terem sido comprovadas que funcionam. Estou procurando contra-pontos que afirmam que sim, essas funções ainda precisam ser testadas, porque: ... Ou que sim, essas não precisam ser testadas ...


32
Não é verdade para todas as funções que, a menos que você altere seu código, se funcionou ontem, funcionará amanhã também? O ponto é que o software foi alterado.
precisa saber é o seguinte

3
Porque ninguém jamais escreverá override_function('pow', '$a,$b', 'return $a * $b;');em sã consciência ... ou tentará reescrevê-lo para lidar com números complexos.

8
Então ... hum ... que "provou ser um código sem erros" ... tem um bug .

Para pequenas funções como essas, convém considerar o teste baseado em propriedades. O teste baseado em propriedades gera automaticamente casos de teste e testes para invariantes pré-determinados. Eles fornecem documentação através dos invariantes, o que os torna úteis para se manter por perto. Para funções simples como essa, elas são perfeitas.
Martijn

6
Além disso, essa função é uma excelente candidata a uma mudança na forma de Horner, (($a*$x + $b)*$x + $c)*$x + $dque é fácil de errar, mas geralmente é consideravelmente mais rápida. Só porque você acha que não vai mudar, não significa que não.
Michael Anderson

Respostas:


78

Teste de regressão

É tudo sobre testes de regressão .

Imagine o próximo desenvolvedor observando seu método e notando que você está usando números mágicos. Foi-lhe dito que os números mágicos são maus, então ele cria duas constantes, uma para o número dois, a outra para o número três - não há nada errado em fazer essa mudança; não é como se ele estivesse modificando sua implementação já correta.

Distraído, ele inverte duas constantes.

Ele confirma o código e tudo parece funcionar bem, porque não há testes de regressão em execução após cada confirmação.

Um dia (pode ser semanas depois), algo quebra em outro lugar. E em outro lugar, quero dizer, no local completamente oposto da base de código, que parece não ter nada a ver com polynominalfunção. Horas de depuração dolorosa levam ao culpado. Durante esse período, o aplicativo continua com falha na produção, causando muitos problemas aos seus clientes.

Manter os testes originais que você escreveu pode impedir essa dor. O desenvolvedor distraído confirmaria o código e quase imediatamente veria que ele quebrou alguma coisa; esse código nem chegará à produção. Os testes de unidade também serão muito precisos sobre a localização do erro . Resolver isso não seria difícil.

Um efeito colateral ...

Na verdade, a maioria das refatorações é fortemente baseada em testes de regressão. Faça uma pequena alteração. Teste. Se passar, está tudo bem.

O efeito colateral é que, se você não tiver testes, praticamente qualquer refatoração se torna um risco enorme de quebrar o código. Como são muitos os casos, já é difícil explicar ao gerenciamento que a refatoração deve ser feita; seria ainda mais difícil após as tentativas anteriores de refatoração introduzirem vários bugs.

Por ter um conjunto completo de testes, você incentiva a refatoração e, portanto, melhor, um código mais limpo. Sem risco, torna-se muito tentador refatorar mais, regularmente.

Mudanças nos requisitos

Outro aspecto essencial é que os requisitos mudam. Você pode ser solicitado a lidar com números complexos e, de repente, é necessário pesquisar no log de controle de versão para encontrar os testes anteriores, restaurá-los e começar a adicionar novos testes.

Por que todo esse aborrecimento? Por que remover testes para adicioná-los mais tarde? Você poderia tê-los mantido em primeiro lugar.


Nota pedante: nada é isento de riscos. ;)
jpmc26

2
A regressão é a razão número um dos testes para mim - mesmo que seu código seja claro sobre responsabilidades e use apenas o que é passado (por exemplo, sem globais), é muito fácil quebrar algo por acidente. Os testes não são perfeitos, mas ainda são ótimos (e quase sempre impedem que o mesmo bug apareça novamente, algo que a maioria dos clientes não gosta ). Se seus testes funcionarem, não haverá custo de manutenção. Se eles pararem de funcionar, você provavelmente encontrou um bug. Estou trabalhando em um aplicativo herdado com milhares de globais - sem testes, não ousaria fazer muitas das alterações necessárias.
Luaan

Outra nota pedante: Alguns números "mágicos" estão realmente corretos e substituí-los por constantes é uma má idéia.
Deduplicator

3
@Duplicador nós sabemos disso, mas muitos programadores juniores treinados especialmente na universidade são extremamente zelosos em seguir cegamente mantras. E "números mágicos são maus" é um deles. Outra é "qualquer coisa usada mais de uma vez deve ser refatorada em um método", levando em casos extremos a uma infinidade de métodos contendo apenas uma única declaração (e então eles se perguntam por que o desempenho caiu repentinamente, nunca aprenderam sobre pilhas de chamadas).
jwenting

@jwenting: Acabei de adicionar essa nota porque a MainMa escreveu "não há nada errado em fazer essa alteração".
Deduplicator

45

Porque nada é tão simples que não pode haver erros.

Seu código, apesar de aparentá-lo, parece estar livre de erros. Na verdade, é uma representação programática simples de uma função polinomial.

Exceto que tem um bug ...

public function polynominal($a, $b, $c, $d)
{
    return  $a * pow($x, 3) + $b * pow($x, 2) + $c * $x + $d;
}

$xnão é definido como uma entrada para o seu código e, dependendo do idioma ou do tempo de execução ou do escopo, sua função pode não funcionar, causar resultados inválidos ou travar uma nave espacial .


Termo aditivo:

Embora você possa considerar seu código livre de bugs por enquanto, é difícil dizer quanto tempo isso permanece. Embora se possa argumentar que escrever um teste para um pedaço de código tão trivial não vale a pena, depois de escrever o teste, o trabalho está concluído e excluí-lo está excluindo uma proteção segura tangível.

Digno de nota adicional são os serviços de cobertura de código (como coveralls.io) que fornecem uma boa indicação da cobertura fornecida por um conjunto de testes. Ao cobrir todas as linhas de código, você fornece uma métrica decente da quantidade (se não da qualidade) dos testes realizados. Em combinação com muitos testes pequenos, esses serviços pelo menos informam onde não procurar um bug quando isso acontecer.

Por fim, se você já tem um teste escrito, mantenha-o . Como a economia de espaço ou tempo decorrente da exclusão, provavelmente será muito menor do que a dívida técnica posteriormente, se ocorrer um erro.


Existe uma solução alternativa: mudar para idiomas não dinâmicos e não fracos. Os testes unitários são geralmente uma solução alternativa para a verificação em tempo de compilação insuficientemente forte e algumas línguas são uma espécie de bom neste en.wikipedia.org/wiki/Agda_(programming_language)
Den

21

Sim. Se pudéssemos dizer com 100% de confiança, com certeza: essa função nunca será editada e nunca será executada em um contexto que poderia causar falhas - se pudéssemos dizer isso, poderíamos interromper os testes e economizar alguns milissegundos a cada Construção de IC.

Mas nós não podemos. Ou, não podemos com muitas funções. E é mais simples ter uma regra de executar todos os testes o tempo todo do que se esforçar para determinar exatamente com qual limiar de confiança estamos satisfeitos e com quanta confiança temos na imutabilidade e infalibilidade de qualquer função.

E o tempo de processamento é barato. Esses milissegundos salvos, mesmo multiplicados muitas vezes, não são suficientes para justificar o tempo gasto em todas as funções a serem perguntadas: temos confiança suficiente para nunca precisar testá-lo novamente?


Eu acho que é um ponto muito bom que você está trazendo aqui. Já consigo ver dois caras passando horas discutindo sobre a manutenção de testes para algumas pequenas funcionalidades.
Kapol

@ Kapol: não é um argumento, uma reunião para determinar o que pode e o que pode permanecer, e que critérios devem ser usados ​​para decidir, bem como onde documentar os critérios e quem assina a decisão final ...
jmoreno

12

Tudo o que foi dito nas outras respostas está correto, mas vou acrescentar mais uma.

Documentação

Os testes de unidade, se bem escritos, podem explicar ao desenvolvedor exatamente o que uma função faz, quais são suas expectativas de entrada / saída e, mais importante, qual comportamento pode ser esperado dela.

Isso pode facilitar a detecção de um bug e diminuir a confusão.

Nem todo mundo se lembra de polinômios, geometria ou até álgebra :) Mas um bom teste de unidade verificado no controle de versão será lembrado por mim.

Para um exemplo de como pode ser útil como documentação, veja a introdução do Jasmine: http://jasmine.github.io/edge/introduction.html Aguarde alguns segundos para carregar e role para baixo. Você verá toda a API do Jasmine documentada como saída de teste de unidade.

[Atualização com base nos comentários do @Warbo] Os testes também estão atualizados, pois, se não estiverem, eles falharão, o que geralmente causará uma falha na compilação se o IC for usado. A documentação externa é alterada independentemente do código e, portanto, não está necessariamente atualizada.


1
Há uma grande vantagem em usar testes como (uma parte da) documentação que você deixou implícita: eles são verificados automaticamente. Outras formas de documentação, por exemplo. comentários explicativos ou trechos de código em uma página da Web podem ficar desatualizados à medida que o código evolui. Os testes de unidade acionarão uma falha se não forem mais precisos. Essa página de jasmim é um exemplo extremo disso.
Warbo

Gostaria de acrescentar que algumas linguagens, como D e Rust, integram a geração de documentação aos testes de unidade, para que você possa ter o mesmo trecho de código compilado em um teste de unidade e inserido na documentação HTML
Idan Arye

2

Reality Check

Eu estive em ambientes desafiadores nos quais os testes são "uma perda de tempo" durante o orçamento e o cronograma e, em seguida, "uma parte fundamental da garantia da qualidade" quando o cliente está lidando com bugs, então minha opinião é mais fluida do que outros.

Você tem um orçamento. Seu trabalho é obter o melhor produto possível dentro desse orçamento, seja qual for a definição de "melhor" que você possa juntar (não é uma palavra fácil de definir). Fim da história.

O teste é uma ferramenta em seu inventário. Você deve usá-lo, porque é uma boa ferramenta , com um longo histórico de economia de milhões, ou talvez bilhões de dólares. Se tiver uma chance, você deve adicionar testes a essas funções simples. Pode salvar sua pele algum dia.

Mas no mundo real, com restrições de orçamento e cronograma, isso pode não acontecer. Não se torne escravo do procedimento. As funções de teste são boas, mas, em algum momento, pode ser melhor gastar suas horas de trabalho escrevendo a documentação do desenvolvedor em palavras, em vez de código, para que o próximo desenvolvedor não precise tanto de testes. Ou pode ser melhor gasto refatorando a base de código, para que você não precise se manter tão difícil quanto um animal. Ou talvez seja melhor gastar esse tempo conversando com seu chefe sobre o orçamento e o cronograma, para que ele entenda melhor o que está oferecendo quando a próxima rodada de financiamento chegar ao fim.

Desenvolvimento de software é um equilíbrio. Sempre conte o custo de oportunidade de qualquer coisa que esteja fazendo para garantir que não haja uma maneira melhor de gastar seu tempo.


4
Nesse caso, já havia um teste; portanto, a questão não é se você deve escrevê-los, mas se deve confirmá-los e executá-los a cada build ou release ou qualquer agendamento existente para executar todos os testes.
Paŭlo Ebermann

0

Sim, mantenha os testes, mantenha-os em execução e mantenha-os aprovados.

Os testes de unidade estão lá para proteger você (e outros) de si mesmo (e deles mesmos).

Por que manter os testes é uma boa idéia;

  • Validar a funcionalidade dos requisitos anteriores em face de novos requisitos e funcionalidade adicional
  • Verifique se os exercícios de refatoração estão corretos
  • Documentação interna - é assim que o código deve ser usado
  • Teste de regressão, as coisas mudam
    • As alterações quebram o código antigo?
    • As alterações requerem mais solicitações ou atualizações de alterações nas funções ou códigos atuais?
  • Como os testes já foram escritos, guarde-os; já é tempo e dinheiro gastos que reduzirão os custos de manutenção mais adiante

2
Isso não parece oferecer nada substancial sobre as outras respostas que foram fornecidas.

1
Ocorreu um problema ao publicá-lo anteriormente, mas, em retrospecto, é um bom resumo. Vou removê-lo.
Niall

-1

Documentação do desenvolvedor

  • Como eu (como outro desenvolvedor) sei que isso foi testado?
  • Se eu quiser corrigir um bug na função independente , como sei que não estou introduzindo um bug que você já considerou?
  • Indicador de complexidade: o número de testes pode ser uma boa medida da complexidade de algo. Isso pode indicar que você não deve tocá-lo porque está maduro e estável ou que está inchado e acoplado.

Documentação do Usuário

  • Com um documento pobre pendente, posso verificar se {zero, valores negativos, conjuntos vazios etc.) são aceitos e qual é o valor de retorno esperado.
  • Também fornece um bom exemplo de como devo usar o objeto / função

1
isso não parece oferecer nada substancial sobre os pontos apresentados e explicados em respostas anteriores. Mesmo "bom resumo" já foi publicado (para o meu gosto não é tão bom, mas oh bem)
mosquito
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.