Nosso colega promove testes de unidade de escrita como realmente nos ajudando a refinar nosso design e refatorar coisas, mas não vejo como. Se eu estiver carregando um arquivo CSV e analisá-lo, como o teste de unidade (validando os valores nos campos) me ajudará a verificar meu design? Ele mencionou acoplamento e modularidade, etc., mas para mim isso não faz muito sentido - mas eu não tenho muita base teórica.
Isso não é o mesmo que a pergunta que você marcou como duplicada, eu estaria interessado em exemplos reais de como isso ajuda, não apenas na teoria dizendo "isso ajuda". Gosto da resposta abaixo e do comentário, mas gostaria de saber mais.
design
unit-testing
object-oriented-design
Usuário039402
fonte
fonte
Respostas:
Os testes de unidade não apenas facilitam o design, mas esse é um dos principais benefícios.
A escrita do primeiro teste elimina a modularidade e a estrutura de código limpa.
Quando você escreve seu código primeiro, você descobrirá que quaisquer "condições" de uma determinada unidade de código são naturalmente enviadas para dependências (geralmente através de zombarias ou stubs) quando você as assume em seu código.
"Com a condição x, esperar o comportamento y", muitas vezes se tornará um esboço de fornecimento
x
(que é um cenário no qual o teste precisa verificar o comportamento do componente atual) ey
se tornará uma farsa, uma chamada à qual será verificada em o final do teste (a menos que seja um "deve retornary
", nesse caso, o teste apenas verificará o valor de retorno explicitamente).Então, depois que esta unidade se comportar conforme especificado, você passa a escrever as dependências (para
x
ey
) que você descobriu.Isso torna a escrita de código modular e limpo um processo muito fácil e natural, onde, caso contrário, é fácil desfocar responsabilidades e unir comportamentos sem perceber.
Escrever testes posteriormente informará quando seu código está mal estruturado.
Quando escrever testes para um pedaço de código se torna difícil porque há muitas coisas para stub ou zombar, ou porque as coisas estão muito juntas, você sabe que tem melhorias a fazer no seu código.
Quando "alterar testes" se torna um fardo, porque há muitos comportamentos em uma única unidade, você sabe que possui melhorias a serem feitas no seu código (ou simplesmente na sua abordagem para escrever testes - mas esse não é geralmente o caso na minha experiência) .
Quando seus cenários se tornam muito complicados ("se
x
ey
ez
depois ...") porque você precisa abstrair mais, você sabe que possui melhorias a serem feitas no seu código.Quando você termina os mesmos testes em dois equipamentos diferentes devido à duplicação e redundância, sabe que possui melhorias a serem feitas no seu código.
Aqui está uma excelente palestra de Michael Feathers demonstrando a estreita relação entre testabilidade e design no código (originalmente publicado por displayName nos comentários). A palestra também aborda algumas queixas e conceitos errôneos sobre bom design e testabilidade em geral.
fonte
O melhor dos testes de unidade é que eles permitem que você use seu código como outros programadores o usarão.
Se o seu código for desajeitado para teste de unidade, provavelmente será difícil de usar. Se você não pode injetar dependências sem pular obstáculos, seu código provavelmente será inflexível de usar. E se você precisar gastar muito tempo configurando dados ou descobrindo em que ordem fazer as coisas, seu código em teste provavelmente tem muito acoplamento e será difícil trabalhar com isso.
fonte
Demorei um pouco para perceber, mas o benefício real (para mim, sua milhagem pode variar) de se fazer desenvolvimento orientado a testes ( usando testes de unidade) é que você precisa fazer o design da API antecipadamente !
Uma abordagem típica do desenvolvimento é primeiro descobrir como resolver um determinado problema e, com esse conhecimento e projeto de implementação inicial, de alguma forma, para invocar sua solução. Isso pode dar alguns resultados bastante interessantes.
Ao fazer o TDD, você deve escrever primeiro o código que usará sua solução. Parâmetros de entrada e saída esperada para garantir que esteja correto. Isso, por sua vez, exige que você descubra o que realmente precisa para fazer, para poder criar testes significativos. Então, e somente então, você implementa a solução. Também é minha experiência que, quando você sabe exatamente o que seu código deve alcançar, ele fica mais claro.
Depois, os testes de unidade após a implementação ajudam a garantir que a refatoração não interrompa a funcionalidade e fornecem documentação sobre como usar seu código (que você sabe que está correto quando o teste foi aprovado!). Mas estes são secundários - o maior benefício é a mentalidade de criar o código em primeiro lugar.
fonte
Eu concordo 100% de que os testes de unidade nos ajudam "a refinar nosso design e refatorar as coisas".
Penso se eles o ajudarão a fazer o design inicial . Sim, eles revelam falhas óbvias e forçam você a pensar em "como posso tornar o código testável"? Isso deve levar a menos efeitos colaterais, configurações e configurações mais fáceis, etc.
No entanto, na minha experiência, testes de unidade excessivamente simplistas, escritos antes que você realmente entenda qual deve ser o design (é certo que é um exagero do TDD de núcleo duro, mas os codificadores muitas vezes escrevem um teste antes que pensem muito) geralmente levam à anemia modelos de domínio que expõem muitos internos.
Minha experiência com TDD foi há vários anos, então estou interessado em saber quais técnicas mais recentes podem ajudar na escrita de testes que não influenciam muito o design subjacente. Obrigado.
fonte
O teste de unidade permite que você veja como as interfaces entre as funções funcionam e, muitas vezes, fornece informações sobre como melhorar o design local e o design geral. Além disso, se você desenvolver seus testes de unidade enquanto desenvolve seu código, terá um conjunto de testes de regressão pronto. Não importa se você está desenvolvendo uma interface do usuário ou uma biblioteca de back-end.
Depois que o programa é desenvolvido (com testes de unidade), conforme os erros são descobertos, você pode adicionar testes para confirmar que os erros foram corrigidos.
Eu uso o TDD para alguns dos meus projetos. Esforço-me bastante na elaboração de exemplos extraídos de livros didáticos ou de papéis considerados corretos e testo o código que estou desenvolvendo usando esse exemplo. Quaisquer mal-entendidos que tenho sobre os métodos se tornam muito aparentes.
Costumo ser um pouco mais flexível do que alguns de meus colegas, pois não me importo se o código for escrito primeiro ou se o teste for escrito primeiro.
fonte
Quando você deseja testar o seu analisador de unidade, detectando corretamente a delimitação de valor, pode passar uma linha a partir de um arquivo CSV. Para tornar seu teste direto e curto, você pode testá-lo através de um método que aceita uma linha.
Isso fará com que você separe automaticamente a leitura de linhas da leitura de valores individuais.
Em outro nível, talvez você não queira colocar todos os tipos de arquivos CSV físicos em seu projeto de teste, mas faça algo mais legível, apenas declarando uma grande cadeia de CSV dentro do seu teste para melhorar a legibilidade e a intenção do teste. Isso levará você a desacoplar seu analisador de qualquer E / S que você faria em outro lugar.
Apenas um exemplo básico, comece a praticar, você sentirá a magia em algum momento (eu tenho).
fonte
Simplificando, escrever testes de unidade ajuda a expor falhas no seu código.
Este guia espetacular para escrever código testável , escrito por Jonathan Wolter, Russ Ruffer e Miško Hevery, contém vários exemplos de como as falhas no código que inibem o teste também impedem a reutilização fácil e a flexibilidade do mesmo código. Portanto, se seu código é testável, é mais fácil de usar. A maioria dos "costumes" são dicas ridiculamente simples que aprimoram bastante o design do código ( Dependency Injection FTW).
Por exemplo: É muito difícil testar se o método computeStuff funciona corretamente quando o cache começa a despejar coisas. Isso ocorre porque você deve adicionar porcaria manualmente ao cache até que o "bigCache" esteja quase cheio.
No entanto, quando usamos injeção de dependência, é muito mais fácil testar se o método computeStuff funciona corretamente quando o cache começa a despejar coisas. Tudo o que fazemos é criar um teste em que chamamos de
new HereIUseDI(buildSmallCache());
Aviso, temos um controle mais matizado do objeto e ele paga dividendos imediatamente.Benefícios semelhantes podem ser obtidos quando nosso código exige dados que geralmente são mantidos em um banco de dados ... basta transmitir EXATAMENTE os dados necessários.
fonte
Dependendo do significado de 'testes de unidade', não acho que os testes de unidade de nível realmente baixo facilitem o bom design, tanto quanto os testes de integração de nível um pouco mais alto - testes que testam um grupo de atores (classes, funções, o que for) seu código combina adequadamente para produzir um monte de comportamentos desejáveis que foram acordados entre a equipe de desenvolvimento e o proprietário do produto.
Se você pode escrever testes nesses níveis, isso leva você a criar um código legal, lógico e parecido com a API que não requer muitas dependências malucas - o desejo de ter uma configuração de teste simples naturalmente o levará a não ter muitas dependências malucas ou código fortemente associado.
Não se engane: os testes de unidade podem levar a um design ruim, bem como a um bom design. Vi os desenvolvedores pegar um pouco de código que já possui um bom design lógico e uma única preocupação, separá-lo e introduzir mais interfaces apenas para fins de teste e, como resultado, tornar o código menos legível e mais difícil de mudar , além de possivelmente ter mais bugs, se o desenvolvedor decidir que ter muitos testes de unidade de baixo nível significa que eles não precisam ter testes de nível superior. Um exemplo favorito em particular é um bug que eu consertei onde havia muitos códigos 'testáveis' e muito detalhados, relacionados à obtenção de informações dentro e fora da área de transferência. Tudo dividido e dissociado em níveis muito pequenos de detalhes, com muitas interfaces, muitas zombarias nos testes e outras coisas divertidas. Apenas um problema: não havia nenhum código que realmente interagisse com o mecanismo da área de transferência do sistema operacional,
Os testes de unidade podem definitivamente orientar o seu design - mas eles não o guiam automaticamente para um bom design. Você precisa ter idéias sobre o que é um bom design que vai além de apenas 'esse código é testado, portanto, é testável e, portanto, é bom'.
É claro que se você é uma daquelas pessoas para quem 'testes de unidade' significa 'qualquer teste automatizado que não seja conduzido pela interface do usuário', alguns desses avisos podem não ser tão relevantes - como eu disse, acho que os mais altos Os testes de integração de nível de nível geralmente são os mais úteis quando se trata de direcionar seu design.
fonte
Os testes de unidade podem ajudar na refatoração quando o novo código passa em todos os testes antigos .
Digamos que você implementou uma classificação de bolhas porque estava com pressa e não estava preocupado com o desempenho, mas agora deseja uma classificação rápida, porque os dados estão ficando mais longos. Se todos os testes forem aprovados, as coisas parecem boas.
É claro que os testes precisam ser abrangentes para fazer isso funcionar. No meu exemplo, seus testes podem não abranger a estabilidade, porque isso não preocupava o bubblesort.
fonte
Eu descobri que os testes de unidade são mais valiosos para facilitar a manutenção a longo prazo de um projeto. Quando volto a um projeto depois de meses e não me lembro de muitos detalhes, a execução de testes me impede de quebrar as coisas.
fonte