Código de afirmações em excesso tem cheiro?

19

Eu realmente me apaixonei por testes de unidade e TDD - estou infectado por testes.

No entanto, o teste de unidade é normalmente usado para métodos públicos. Às vezes, também tenho que testar algumas suposições em métodos particulares, porque algumas são "perigosas" e a refatoração não pode ajudar mais. (Eu sei, estruturas de teste permitem testar métodos privados).

Portanto, tornou-se um hábito meu que a primeira e a última linha de um método privado sejam ambas afirmações.

No entanto, notei que tenho tendência a usar asserções em métodos públicos (assim como privados) apenas "para ter certeza". Poderia ser "duplicação de teste", uma vez que as suposições de método público são testadas externamente pela estrutura de teste de unidade?

Alguém poderia pensar em muitas afirmações como um cheiro de código?

Florents Tselai
fonte
1
essas afirmações estão ativadas ou desativadas no lançamento?
jk.
Eu trabalho principalmente com Java, onde as asserções devem ser ativadas manualmente.
Florents Tselai
Só encontrei este "Afirma aumento de produtividade por rastrear erros" programmers.stackexchange.com/a/16317/32342
Florents Tselai
como você define "muitos"?
haylem
@haylem Existem afirmações "demais" se outro programador ler o código e disser "Estou cansado dessas afirmações".
Florents Tselai

Respostas:

26

Essas afirmações são realmente úteis para testar suas suposições, mas também servem a outro propósito realmente importante: documentação. Qualquer leitor de um método público pode ler as declarações para determinar rapidamente as condições pré e pós, sem precisar olhar para a suíte de testes. Por esse motivo, recomendo que você mantenha essas declarações por motivos de documentação, em vez de por motivos de teste. Tecnicamente, você está duplicando as asserções, mas elas servem a dois propósitos diferentes e são muito úteis em ambos.

Mantê-los como afirmações é melhor do que simplesmente usar comentários, porque eles verificam ativamente as premissas sempre que são executadas.

Oleksi
fonte
2
Muito bom ponto!
Florents Tselai
1
As asserções são documentação, mas isso não as torna legítimas. Pelo contrário, levanta suspeitas sobre a qualidade dos testes, se parecer que as mesmas afirmações são necessárias mais de uma vez.
Yam Marcovic
1
@YamMarcovic Acho que isso é aceitável porque os dois pedaços de código "duplicados" servem a dois propósitos diferentes e distintos. Eu acho que é útil tê-los nos dois locais, apesar da duplicação. Ter as asserções no método é inestimável para a documentação e elas são obviamente necessárias para o teste.
Oleksi
2
As asserções no código para mim são complementares às asserções de teste de unidade. Você não terá 100% de cobertura de teste e as asserções no código o ajudarão a diminuir os testes de unidade com mais rapidez.
sebastiangeiger
15

Parece que você está tentando fazer um projeto por contrato manualmente.

Fazer DbC é uma boa ideia, mas você deve pelo menos considerar mudar para um idioma que o suporte nativamente (como Eiffel ) ou pelo menos usar uma estrutura de contrato para sua plataforma (por exemplo, Microsoft Code Contracts for .NETé bem legal, sem dúvida a estrutura de contrato mais sofisticada do mercado, ainda mais poderosa que Eiffel). Dessa forma, você pode aproveitar melhor o poder dos contratos. Por exemplo, usando uma estrutura existente, você pode apresentar os contratos em sua documentação ou em um IDE (por exemplo, o Code Contracts for .NET é mostrado no IntelliSense e, se você tiver o VS Ultimate, pode até ser verificado estaticamente por um provador de teoremas automático em tempo de compilação enquanto você digita; da mesma forma, muitas estruturas de contrato Java possuem doclets JavaDoc que extraem automaticamente os contratos na documentação da API JavaDoc).

E mesmo que, na sua situação, não haja alternativa a fazê-lo manualmente, agora você pelo menos sabe como é chamado e pode ler sobre isso.

Então, resumindo: se você está realmente fazendo DbC, mesmo que não o conheça, essas afirmações são perfeitamente boas.

Jörg W Mittag
fonte
4

Sempre que você não tem controle total sobre seus parâmetros de entrada, é uma boa idéia testar antecipadamente se há erros simples. Falha no nulo, por exemplo.

Isso não é duplicação de seus testes, pois eles devem testar se o código falhou adequadamente, devido a parâmetros de entrada incorretos, e depois documentar isso .

Não acho que você deva afirmar os parâmetros de retorno (a menos que você tenha explicitamente um invariável que deseja que o leitor entenda). Este também é o trabalho dos unittests.

Pessoalmente, não gosto da assertdeclaração em Java, pois elas podem ser desativadas e, em seguida, é uma segurança falsa.


fonte
1
+1 Para "Não gosto da declaração assert em Java, pois eles podem ser desativados e, em seguida, é uma segurança falsa".
Florents Tselai
3

Eu acho que o uso de asserções em métodos públicos é ainda mais importante, pois você não controla as entradas e pode ser mais provável que uma suposição seja quebrada.

O teste das condições de entrada deve ser realizado em todos os métodos públicos e protegidos. Se as entradas forem passadas diretamente para um método privado, o teste de suas entradas poderá ser redundante.

O teste das condições de saída (por exemplo, estado do objeto ou que retornam valor! = Null) deve ser realizado nos métodos internos (por exemplo, métodos privados). Se as saídas forem passadas diretamente de um método privado para a saída de um método público sem cálculos adicionais ou alterações de estado interno, o teste das condições de saída do método público poderá ser redundante.

Concordo com Oleksi, no entanto, que a redundância pode aumentar a legibilidade e também a manutenção (se a atribuição direta ou o retorno não for mais o caso no futuro).

Danny Varod
fonte
4
Se uma condição de erro ou condição de argumento inválida puder ser causada pelo usuário (chamador) da interface pública, isso deve receber o tratamento completo adequado para o tratamento de erros. Isso significa formatar uma mensagem de erro significativa para o usuário final, acompanhada de código de erro, log e tentativas de recuperar dados ou restaurar para um estado sadio. Substituir o tratamento adequado de erros por asserções tornará o código menos robusto e mais difícil de solucionar.
precisa
As asserções podem (e em muitos casos deveriam) incluir exceções de registro e lançamento (ou códigos de erro em C--).
precisa
3

É difícil ser independente de idioma sobre esse problema, pois os detalhes de como as asserções e o tratamento "adequado" de erros / exceções são implementados têm influência na resposta. Aqui está o meu $ 0,02, com base no meu conhecimento de Java e C ++.

Afirmar métodos privados é uma coisa boa, desde que você não exagere e os coloque em todos os lugares . Se você está aplicando métodos realmente simples ou verificando repetidamente coisas como campos imutáveis, está desorganizando o código desnecessariamente.

Geralmente, é melhor evitar afirmações em métodos públicos. Você ainda deve fazer coisas como verificar se o contrato do método não foi violado, mas, se for, você deve lançar exceções de tipo adequado com mensagens significativas, reverter o estado sempre que possível, etc. (o que @rwong chama de "completo tratamento adequado de manipulação de erros ").

De um modo geral, você só deve usar asserções para ajudar no seu desenvolvimento / depuração. Você não pode presumir que quem usa sua API terá afirmações ativadas quando usar seu código. Embora eles tenham alguma utilidade para ajudar a documentar o código, geralmente existem maneiras melhores de documentar as mesmas coisas (por exemplo, documentação do método, exceções).

vaughandroid
fonte
2

Acrescentando à lista de respostas (principalmente) excepcionais, outro motivo para muitas afirmações e duplicação, é que você não sabe como, quando ou por quem a classe será modificada ao longo da vida. Se você estiver escrevendo um código de descarte que estará morto em um ano, não é uma preocupação. Se você estiver escrevendo um código que será usado em mais de 20 anos, ele parecerá bem diferente - e uma suposição que você tenha feito pode não ser mais válida. O cara que faz essa alteração agradece pelas declarações.

Além disso, nem todos os programadores são perfeitos - novamente, as afirmações significam que um desses "deslizes" não será propagado muito longe.

Não subestime o efeito que essas afirmações (e outras verificações sobre premissas) terão na redução do custo de manutenção.

mattnz
fonte
0

Ter afirmações duplicadas não é errado por si só, mas as afirmações "apenas para garantir" não são as melhores. Realmente se resume ao que exatamente cada teste está tentando realizar. Afirme apenas o que é absolutamente necessário. Se um teste usa o Moq para verificar se um método é chamado, não importa realmente o que acontece depois que o método é chamado, o teste se preocupa apenas em garantir que o método seja chamado.

Se todos os testes unitários tiverem o mesmo conjunto de afirmações, exceto um ou dois, quando você refatorar um pouco, todos os testes poderão falhar pelo mesmo motivo, em vez de mostrar o verdadeiro impacto do refator. Você pode acabar em uma situação em que executa todos os testes, todos eles falham porque cada teste tem as mesmas afirmações, você corrige essa falha, executa os testes novamente, eles falham por outro motivo, você corrige essa falha. Repita até terminar.

Considere também a manutenção do seu projeto de teste. O que acontece quando você adiciona um novo parâmetro ou a saída é ajustada de vez em quando? Você precisaria voltar a vários testes e alterar uma declaração ou adicionar uma declaração? E, dependendo do seu código, você pode ter que configurar muito mais apenas para garantir que todas as declarações sejam aprovadas.

Eu posso entender o fascínio de querer incluir afirmações duplicadas no teste de unidade, mas é realmente um exagero. Eu segui o mesmo caminho com meu primeiro projeto de teste. Eu me afastei porque a manutenção sozinha estava me deixando louco.

bwalk2895
fonte
0

No entanto, o teste de unidade é usado para métodos públicos.

Se sua estrutura de teste permitir acessadores, os métodos particulares de teste de unidade também serão uma boa ideia (IMO).

Alguém poderia pensar em muitas afirmações como um cheiro de código?

Eu faço. As asserções são válidas, mas seu primeiro objetivo deve ser fazer com que o cenário nem seja possível ; não apenas isso não acontece.

Telastyn
fonte
-2

É uma prática ruim.

Você não deve testar métodos públicos / privados. Você deve testar a classe como um todo. Apenas verifique se você tem uma boa cobertura.

Se você seguir a maneira (primitiva) de testar cada método separadamente como regra geral, será extremamente difícil refatorar sua classe no futuro. E essa é uma das melhores coisas que o TDD permite que você faça.

Além disso, é duplicação. Além disso, sugere que o autor do código realmente não sabia o que estava fazendo. E o engraçado é que você faz .

Algumas pessoas tratam seus testes com menos cuidado do que seu código de produção. Eles não deveriam. Se alguma coisa, minha experiência sugere tratar os testes com mais cuidado. Eles valem a pena.

Yam Marcovic
fonte
-1 O teste unitário de uma classe como um todo pode ser perigoso, pois você acaba testando mais de uma coisa por teste. Isso faz testes realmente frágeis. Você deve testar um comportamento ao mesmo tempo, o que geralmente corresponde a testar apenas um método por teste.
Oleksi
Também em relação a "isso sugere que o autor do código realmente não sabia o que estava fazendo [...] você". Os desenvolvedores futuros podem não ser tão claros sobre o que um método específico faz. Nesse caso, ter condições pré e pós na forma de afirmações pode ser inestimável para a compreensão de um pedaço de código.
Oleksi
2
@Oleksi Um bom teste é sobre cenários de uso válidos, não sobre afirmar todos os mínimos detalhes irrelevantes. Afirmações redundantes são de fato um cheiro de código devido à intenção obscura e são apenas outro exemplo ruim de programação defensiva.
Yam Marcovic