O código duplicado é mais tolerável nos testes de unidade?
-
02-07-2019 - |
Pergunta
Eu arruinei vários testes de unidade há algum tempo, quando passei e os refatorei para torná -los mais SECO-A intenção de cada teste não estava mais clara. Parece que há uma troca entre a legibilidade e a manutenção dos testes. Se eu deixar o código duplicado em testes de unidade, eles serão mais legíveis, mas se eu mudar o SUT, Vou ter que rastrear e alterar cada cópia do código duplicado.
Você concorda que essa compensação existe? Em caso afirmativo, você prefere que seus testes sejam legíveis ou sustentáveis?
Solução
O código duplicado é um cheiro no código de teste de unidade tanto quanto em outro código. Se você tiver código duplicado nos testes, dificulta o refattor do código de implementação, porque você tem um número desproporcional de testes para atualizar. Os testes devem ajudá -lo a refatorar com confiança, em vez de ser uma grande carga que impede seu trabalho no código que está sendo testado.
Se a duplicação estiver em equipamento, considere fazer mais uso do setUp
método ou fornecendo mais (ou mais flexível) Métodos de criação.
Se a duplicação estiver no código manipulando o SUT, pergunte-se por que vários testes de "unidade" estão exercendo exatamente a mesma funcionalidade.
Se a duplicação estiver nas afirmações, talvez você precise de alguns Asserções personalizadas. Por exemplo, se vários testes tiverem uma série de afirmações como:
assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())
Então talvez você precise de um único assertPersonEqual
método, para que você possa escrever assertPersonEqual(Person('Joe', 'Bloggs', 23), person)
. (Ou talvez você simplesmente precise sobrecarregar o operador de igualdade em Person
.)
Como você menciona, é importante que o código de teste seja legível. Em particular, é importante que o intenção de um teste é claro. Acho que, se muitos testes parecerem os mesmos (por exemplo, três quartos das linhas iguais ou praticamente iguais), é difícil identificar e reconhecer as diferenças significativas sem lê-las e compará-las cuidadosamente. Então eu acho que a refatoração para remover a duplicação ajuda A legibilidade, porque todas as linhas de cada método de teste são diretamente relevantes para o objetivo do teste. Isso é muito mais útil para o leitor do que uma combinação aleatória de linhas diretamente relevantes e linhas que são apenas caldeiras.
Dito isto, às vezes os testes exercem situações complexas semelhantes, mas ainda significativamente diferentes, e é difícil encontrar uma boa maneira de reduzir a duplicação. Use o senso comum: se você acha que os testes são legíveis e deixam sua intenção clara, e você se sente confortável em talvez precisar atualizar mais do que um número teoricamente mínimo de testes ao refatorar o código invocado pelos testes, aceite a imperfeição e mova -se para algo mais produtivo. Você sempre pode voltar e refatorar os testes mais tarde, quando a inspiração ocorre!
Outras dicas
A legibilidade é mais importante para testes. Se um teste falhar, você deseja que o problema seja óbvio. O desenvolvedor não deve ter que percorrer muitos códigos de teste fortemente fatorados para determinar exatamente o que falhou. Você não deseja que seu código de teste se torne tão complexo que precisa escrever testes de teste de unidade.
No entanto, a eliminação de duplicação geralmente é uma coisa boa, desde que não obscureça nada, e a eliminação da duplicação em seus testes pode levar a uma API melhor. Apenas certifique -se de não passar pelo ponto de diminuição de retornos.
O código e os testes de implementação são animais diferentes e as regras de fatoração se aplicam de maneira diferente a eles.
O código ou estrutura duplicada é sempre um cheiro no código de implementação. Quando você começa a ter o Boilerplate na implementação, você precisa revisar suas abstrações.
Por outro lado, o código de teste deve manter um nível de duplicação. A duplicação no código de teste atinge dois objetivos:
- Mantendo os testes dissociados. O acoplamento excessivo de teste pode dificultar a alteração de um único teste de falha que precisa ser atualizado porque o contrato mudou.
- Manter os testes significativos isoladamente. Quando um único teste está falhando, deve ser razoavelmente simples descobrir exatamente o que está testando.
Costumo ignorar a duplicação trivial no código de teste, desde que cada método de teste permaneça mais curto que cerca de 20 linhas. Eu gosto quando o ritmo de configuração-verificação é aparente nos métodos de teste.
Quando a duplicação se aproxima da parte "verificar" dos testes, geralmente é benéfico definir métodos de afirmação personalizados. Obviamente, esses métodos ainda devem testar uma relação claramente identificada que possa ser aparente no nome do método: assertPegFitsInHole
-> bom, assertPegIsGood
-> ruim.
Quando os métodos de teste crescem longos e repetitivos, às vezes acho útil definir modelos de teste de preenchimento em blindagem que recebem alguns parâmetros. Em seguida, os métodos de teste reais são reduzidos a uma chamada para o método de modelo com os parâmetros apropriados.
Quanto a muitas coisas em programação e teste, não há resposta clara. Você precisa desenvolver um gosto, e a melhor maneira de fazer isso é cometer erros.
Você pode reduzir a repetição usando vários sabores diferentes de Métodos de utilidade de teste.
Sou mais tolerante com a repetição no código de teste do que no código de produção, mas às vezes fico frustrado com ele. Quando você muda o design de uma classe e precisa voltar e ajustar 10 métodos de teste diferentes que fazem as mesmas etapas de configuração, é frustrante.
Concordo. O comércio existe, mas é diferente em lugares diferentes.
É mais provável que refatorie o código duplicado para a criação de estado. Mas menos probabilidade de refatorar a parte do teste que realmente exerce o código. Dito isto, se o exercício do código sempre leva várias linhas de código, acho que isso é um cheiro e refatorar o código real em teste. E isso melhorará a legibilidade e a manutenção do código e dos testes.
Jay Fields cunhou a frase de que "as DSLs deveriam estar úmidas, não secas", onde ÚMIDO significa frases descritivas e significativas. Eu acho que o mesmo se aplica a testes também. Obviamente, muita duplicação é ruim. Mas remover a duplicação a todo custo é ainda pior. Os testes devem atuar como especificações de revelação de intenções. Se, por exemplo, você especificar o mesmo recurso de vários ângulos diferentes, é esperado uma certa quantidade de duplicação.
Eu amo o RSPEC por causa disso:
Tem 2 coisas para ajudar -
Grupos de exemplo compartilhados para testar comportamento comum.
Você pode definir um conjunto de testes e, em seguida, 'incluir' que definir seus testes reais.contextos aninhados.
Você pode ter um método de 'configuração' e 'desmontagem' para um subconjunto específico de seus testes, não apenas todos da classe.
Quanto mais cedo isso .NET/Java/Outras estruturas de teste adotarem esses métodos, melhor (ou você pode usar o IronRuby ou Jruby para escrever seus testes, o que eu pessoalmente acho que é a melhor opção)
Eu não acho que exista uma relação entre código mais duplicado e legível. Eu acho que seu código de teste deve ser tão bom quanto seu outro código. O código não repetido é mais legível do que o código duplicado quando bem feito.
Idealmente, os testes de unidade não devem mudar muito assim que estiverem escritos, então eu me inclinaria para a legibilidade.
Ter testes de unidade é o mais discreto possível também ajuda a manter os testes focados na funcionalidade específica que eles estão direcionando.
Com isso dito, tendem a tentar reutilizar certas peças de código que acabo usando repetidamente, como o código de configuração exatamente o mesmo em um conjunto de testes.
Sinto que o código de teste requer um nível semelhante de engenharia que normalmente seria aplicado ao código de produção. Certamente pode haver argumentos feitos em favor da legibilidade e eu concordo que isso é importante.
Na minha experiência, no entanto, acho que os testes bem-fortos são mais fáceis de ler e entender. Se houver 5 testes de que cada um parece o mesmo, exceto uma variável que mudou e a afirmação no final, pode ser muito difícil encontrar o que é esse único item diferente. Da mesma forma, se for fatorado, de modo que apenas a variável que está mudando é visível e a afirmação, é fácil descobrir o que o teste está fazendo imediatamente.
Encontrar o nível certo de abstração quando os testes podem ser difíceis e sinto que vale a pena fazer.
"Refatorou-os para torná-los mais secos-a intenção de cada teste não estava mais clara"
Parece que você teve problemas para fazer a refatoração. Estou apenas adivinhando, mas se isso acabou menos claro, isso não significa que você ainda tem mais trabalho a fazer para fazer testes razoavelmente elegantes que são perfeitamente claros?
É por isso que os testes são uma subclasse do UNITTEST - para que você possa projetar boas suítes de teste corretas, fáceis de validar e limpar.
Nos tempos antigos, tínhamos ferramentas de teste que usavam diferentes linguagens de programação. Era difícil (ou impossível) projetar agradável, fácil de trabalhar com testes.
Você tem todo o poder - qualquer que esteja usando - python, java, c# - então use bem esse idioma. Você pode obter um código de teste bonito que é claro e não muito redundante. Não há troca.