Eu acredito que isso é um equívoco de qualquer maneira que eu possa pensar.
O código de teste que testa o código de produção não é nada parecido. Vou demonstrar em python:
def multiply(a, b):
"""Multiply ``a`` by ``b``"""
return a*b
Então, um teste simples seria:
def test_multiply():
assert multiply(4, 5) == 20
Ambas as funções têm uma definição semelhante, mas ambas fazem coisas muito diferentes. Nenhum código duplicado aqui. ;-)
Também ocorre que as pessoas escrevem testes duplicados tendo essencialmente uma asserção por função de teste. Isso é loucura e já vi pessoas fazendo isso. Isso é uma má prática.
def test_multiply_1_and_3():
"""Assert that a multiplication of 1 and 3 is 3."""
assert multiply(1, 3) == 3
def test_multiply_1_and_7():
"""Assert that a multiplication of 1 and 7 is 7."""
assert multiply(1, 7) == 7
def test_multiply_3_and_4():
"""Assert that a multiplication of 3 and 4 is 12."""
assert multiply(3, 4) == 12
Imagine fazer isso para mais de 1000 linhas de código eficazes. Em vez disso, você testa por 'recurso':
def test_multiply_positive():
"""Assert that positive numbers can be multiplied."""
assert multiply(1, 3) == 3
assert multiply(1, 7) == 7
assert multiply(3, 4) == 12
def test_multiply_negative():
"""Assert that negative numbers can be multiplied."""
assert multiply(1, -3) == -3
assert multiply(-1, -7) == 7
assert multiply(-3, 4) == -12
Agora, quando os recursos são adicionados / removidos, só tenho que considerar adicionar / remover uma função de teste.
Você deve ter notado que eu não apliquei for
loops. Isso ocorre porque repetir algumas coisas é bom. Quando eu aplicaria loops, o código seria muito menor. Mas quando uma afirmação falha, pode ofuscar a saída que exibe uma mensagem ambígua. Se isso ocorrer, em seguida, os testes serão menos útil e você vai precisar de um depurador para inspecionar onde as coisas dão errado.
assert multiply(1,3)
falharia, mas você também não obteria o relatório de teste com falhaassert multiply(3,4)
.def test_shuffle
executa duas declarações.assert multiply(*, *) == *
para que você possa definir umaassert_multiply
função. No cenário atual, isso não importa pela contagem de linhas e pela legibilidade, mas por testes mais longos, você pode reutilizar asserções complicadas, acessórios, código de geração de acessórios, etc. Não sei se essa é uma prática recomendada, mas geralmente faço esta.Não, isto não é verdade.
Os testes têm uma finalidade diferente da sua implementação:
fonte
Não. DRY é sobre escrever código apenas uma vez para executar uma tarefa específica. Testes são a validação de que a tarefa está sendo executada corretamente. É um pouco semelhante a um algoritmo de votação, onde obviamente usar o mesmo código seria inútil.
fonte
Não, o objetivo final do DRY realmente significaria a eliminação de todo o código de produção .
Se nossos testes pudessem ser especificações perfeitas do que queremos que o sistema faça, teríamos de gerar automaticamente o código de produção correspondente (ou binários) automaticamente, removendo efetivamente a base de código de produção em si.
Na verdade, é isso que abordagens como a arquitetura orientada a modelos pretendem alcançar - uma única fonte de verdade projetada pelo homem a partir da qual tudo é derivado pela computação.
Eu não acho que o contrário (se livrar de todos os testes) seja desejável porque:
fonte
Porque às vezes se repetir é bom. Nenhum desses princípios deve ser adotado em todas as circunstâncias, sem dúvida ou contexto. Às vezes, escrevi testes contra uma versão ingênua (e lenta) de um algoritmo, que é uma violação bastante clara do DRY, mas definitivamente benéfica.
fonte
Desde teste de unidade é sobre como fazer alterações não intencionais mais difícil, às vezes pode fazer mudanças intencionais mais difícil, também. Este fato está realmente relacionado ao princípio DRY.
Por exemplo, se você tiver uma função
MyFunction
chamada no código de produção em apenas um local e escrever 20 testes de unidade, poderá facilmente ter 21 lugares no código onde essa função é chamada. Agora, quando você precisa alterar a assinatura deMyFunction
, ou a semântica, ou ambos (porque alguns requisitos mudam), você tem 21 lugares para mudar em vez de apenas um. E o motivo é realmente uma violação do princípio DRY: você repetiu (pelo menos) a mesma chamada de funçãoMyFunction
21 vezes.A abordagem correta para esse caso é a aplicação do princípio DRY ao seu código de teste: ao escrever 20 testes de unidade, encapsule as chamadas
MyFunction
nos testes de unidade em apenas algumas funções auxiliares (idealmente, apenas uma), usadas pelo 20 testes de unidade. Idealmente, você acaba com apenas dois lugares na sua chamada de códigoMyFunction
: um do seu código de produção e um dos seus testes de unidade. Portanto, quando você precisar alterar a assinaturaMyFunction
posteriormente, terá apenas alguns lugares para alterar em seus testes."Alguns lugares" ainda são mais do que "um lugar" (o que você obtém sem testes de unidade), mas as vantagens de ter testes de unidade devem superar em muito a vantagem de ter menos código para alterar (caso contrário, você fará testes de unidade completamente errado).
fonte
Um dos maiores desafios para a construção de software é capturar requisitos; ou seja, para responder à pergunta "o que esse software deve fazer?" O software precisa de requisitos exatos para definir com precisão o que o sistema precisa fazer, mas aqueles que definem as necessidades de sistemas e projetos de software geralmente incluem pessoas que não possuem um software ou formação formal (matemática). A falta de rigor na definição de requisitos forçou o desenvolvimento de software a encontrar uma maneira de validar o software para os requisitos.
A equipe de desenvolvimento se viu traduzindo a descrição coloquial de um projeto em requisitos mais rigorosos. A disciplina de testes se uniu como ponto de verificação para o desenvolvimento de software, para preencher a lacuna entre o que o cliente diz que quer e o que o software entende que ele quer. Tanto os desenvolvedores de software quanto a equipe de qualidade / teste formam o entendimento da especificação (informal) e cada (independentemente) escreve software ou testes para garantir que seu entendimento esteja em conformidade. A inclusão de outra pessoa para entender os requisitos (imprecisos) adicionou perguntas e perspectivas diferentes para aprimorar ainda mais a precisão dos requisitos.
Como sempre houve testes de aceitação, era natural expandir a função de teste para escrever testes automatizados e de unidade. O problema era que isso significava contratar programadores para fazer testes e, assim, você reduzia a perspectiva da garantia de qualidade para os programadores que realizavam testes.
Dito isso, você provavelmente está fazendo testes errados se os testes diferem pouco dos programas reais. A sugestão de Msdy seria focar mais o que nos testes e menos como.
A ironia é que, em vez de capturar uma especificação formal de requisitos a partir da descrição coloquial, a indústria optou por implementar testes de pontos como código para automatizar os testes. Em vez de produzir requisitos formais aos quais o software pode ser construído para responder, a abordagem adotada foi testar alguns pontos, em vez de abordar a criação de software usando lógica formal. Este é um compromisso, mas tem sido bastante eficaz e relativamente bem-sucedido.
fonte
Se você acha que seu código de teste é muito semelhante ao código de implementação, isso pode ser uma indicação de que você está usando demais uma estrutura de simulação. O teste baseado em simulação em um nível muito baixo pode terminar com a configuração de teste semelhante ao método que está sendo testado. Tente escrever testes de nível superior com menor probabilidade de interrupção se você alterar sua implementação (eu sei que isso pode ser difícil, mas se você puder gerenciá-lo, terá um conjunto de testes mais útil como resultado).
fonte
Os testes de unidade não devem incluir uma duplicação do código em teste, como já foi observado.
Eu acrescentaria, no entanto, que os testes de unidade normalmente não são tão SECOS quanto o código de "produção", porque a instalação tende a ser semelhante (mas não idêntica) nos testes ... especialmente se você tiver um número significativo de dependências que você está zombando / fingindo.
É claro que é possível refatorar esse tipo de coisa em um método de instalação comum (ou conjunto de métodos de instalação) ... mas descobri que esses métodos de instalação tendem a ter longas listas de parâmetros e são bastante frágeis.
Então seja pragmático. Se você pode consolidar o código de instalação sem comprometer a manutenção, faça isso de qualquer maneira. Mas se a alternativa for um conjunto complexo e frágil de métodos de configuração, um pouco de repetição nos seus métodos de teste está OK.
Um evangelista local do TDD / BDD coloca desta maneira:
"Seu código de produção deve estar SECO. Mas não há problema em seus testes serem 'úmidos'".
fonte
Isso não é verdade, os testes descrevem os casos de uso, enquanto o código descreve um algoritmo que passa nos casos de uso, o que é mais geral. Pelo TDD, você começa escrevendo casos de uso (provavelmente com base na história do usuário) e depois implementa o código necessário para passar esses casos de uso. Então você escreve um pequeno teste, um pequeno pedaço de código e depois refatora, se necessário, para se livrar das repetições. É assim que funciona.
Por testes, pode haver repetições também. Por exemplo, você pode reutilizar equipamentos, gerar códigos, declarações complicadas, etc. Normalmente, faço isso para evitar bugs nos testes, mas geralmente esqueço de testar primeiro se um teste realmente falha e pode realmente arruinar o dia. , quando você procura o bug no código por meia hora e o teste está errado ... xD
fonte